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

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

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

はじめに

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

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

(前回の記事)

sakitadaiki.hatenablog.com

本記事ではQ25 ~ Q30を深掘りします。

Q25: ヘルパーにはどのようなロジックを置きますか?

回答:

ヘルパーのロジックは、ビューだけをサポートすべきです。ヘルパーの候補としては、複数の異なるビューで必要になる日付フォーマットロジックがよい例です。

Railsには標準でビュー側で記載できるヘルパーメソッドというものがあります。

link_to・・・リンクを配置するヘルパーメソッド
form_with・・・フォーム入力の際に使われるヘルパーメソッド
image_tag・・・画像を描画するヘルパーメソッド

これらのメソッドはビュー側をサポートすることを意図しているように、ヘルパーメソッドのロジックは基本的にはビューのために使われます。

標準だけでなく自作でヘルパーメソッドを作ることができます。試しに作ってみます。ヘルパーファイルはRailsプロジェクトのapp/helpers/ディレクトリ配下に置かれますので、このディレクトリにファイルを作成します。

https://i.gyazo.com/c165999db2e3a43ff6808629f849c68b.png

例えば、ビュー側で投稿した記事を表示させているとします。投稿した記事に対して編集ボタンを表示させたいのですが以下のように記述した場合全てのユーザが投稿した記事を編集できてしまいます。

<div>
    <%= edit_article_path(@article) %>
    <%= @article.title %>
</div>

そこでヘルパー側でcurrent_user?メソッドを定義します。引数であるユーザが現在ログインしているユーザと同じ場合trueを返すメソッドです。

# app/helpers/users_helper.rb
module UsersHelper
    def current_user?(user)
      current_user.id == user.id
  end
end

そしてヘルパーメソッドを用いて以下のように記述するとログインしているユーザの記事だけに編集ボタンを表示することができます。

<div>
    <% if current_user.id == @article.user.id %>
        <%= edit_article_path(@article) %>
    <% end %>
    <%= @article.title %>
</div>

ヘルパーはこのようにビュー側をサポートするロジックを書く役割を担っています。まさにヘルパー(助けるもの)。

Q26: Active Recordについて説明してください

回答:

Active Recordは、モデルとデータベースを対応付けるORM(Object-Relational Mapping)です。Active Recordを用いることで、オブジェクトの読み込みや保存や削除を直接SQLで記述する必要がなくなり、アプリのセットアップがシンプルになります。 Active Recordは、ある程度のSQLインジェクションの保護機能も提供しています。

Active RecordはRubyを使ったままSQLを書くことができるという知識のみで使っていましたので、この際詳しく調べてみましょう。

まずORMについて調べてみました。

オブジェクト関係マッピング(英: Object-relational mapping、O/RM、ORM)とは、データベースとオブジェクト指向プログラミング言語の間の非互換なデータを変換するプログラミング技法である。オブジェクト関連マッピングとも呼ぶ。実際には、オブジェクト指向言語から使える「仮想」オブジェクトデータベースを構築する手法である。オブジェクト関係マッピングを行うソフトウェアパッケージは商用のものもフリーなものもあるが、場合によっては独自に開発することもある。

https://ja.wikipedia.org/wiki/オブジェクト関係マッピング

オブジェクト指向プログラミング言語とDBは本来であれば非互換であるのですが、それを互換性のあるように双方に変換できるようにする技術ということになります。

オブジェクト指向プログラミング言語と書いてあるので、ORMの技術はRuby特有のものではありません。Javaには「Hibernate」というORMのフレームワークがあるらしいです。

ORMによってSQLを直接記述することなくデータを探すことができます。下記は名前が「hogehoge」であるユーザーを取り出しています。

User.find_by(name: "hogehoge")

これをSELECT文で書くと以下のようになります。

SELECT * FROM users WHERE name = "hogehoge"

アプリケーションではデータの保存・削除・取り出しが頻繁に行われますので、その度に生のSQLを書いていたらとても大変です。そこで、Rubyのメソッドを用いることでSQLと同じようにデータを操作することが可能になっています。

Active Recordの種類

  • find
  • create_with
  • distinct
  • eager_load
  • extending
  • from
  • group
  • having
  • includes
  • joins
  • left_outer_joins
  • limit
  • lock
  • none
  • offset
  • order
  • preload
  • readonly
  • references
  • reorder
  • reverse_order
  • select
  • where

railsguides.jp

Q27: Rubyのselfはどんなときに使うかを説明してください

回答:

・クラスメソッドの定義や呼び出しではselfを使う ・クラス内ではselfを用いて現在のクラスを参照する、つまり、あるクラスメソッドから(訳注: 同じクラス内の)別のクラスメソッドを呼び出すときに必須となる ・インスタンスからクラスメソッドを呼び出す場合はself.class.methodという呼び出し方法が必須となる

1つ1つ文章を確認します。

  • クラスメソッドの定義や呼び出しではselfを使う

これは「Q19: クラスメソッドとインスタンスメソッドの違いを説明してください」にて説明しました。クラスメソッドを定義する場合はdef self.メソッド名と定義します。

class Hoge
  def self.hoge
    puts 'hoge'
  end
end
  • クラス内ではselfを用いて現在のクラスを参照する、つまり、あるクラスメソッドから(訳注: 同じクラス内の)別のクラスメソッドを呼び出すときに必須となる

クラス内ではselfは基本的に現在のクラスそのものを指します。

class Hoge
  def self.hoge
    puts self # selfを出力
  end
end

Hoge.hoge # => Hoge

同じクラスのクラスメソッドを別のクラスで実行する場合、selfは現在のクラスを参照するのでselfが必ず必要となります。

class Hoge
  def self.hoge
    puts self # selfを出力
  end

    def self.fuga
        self.hoge # self = Hoge
    end
end

Hoge.fuga # => Hoge (Hoge.hogeを実行しているのと同じ)
  • インスタンスからクラスメソッドを呼び出す場合はself.class.methodという呼び出し方法が必須となる

インスタンスselfを使う場合、そのselfは元のクラスではなくそのインスタンスを参照します。

class Hoge
  def fuga
    p self.class
  end
end
hoge = Hoge.new
hoge.fuga #=> #<Hoge:0x00007f8524034f70> = インスタンス

元のクラスを出力するにはself.classで参照できます。

class Hoge
  def fuga
    p self.class
  end
end
hoge = Hoge.new
hoge.fuga #=> #<Hoge> = クラス

クラスが参照できるのでそこからクラスメソッドを実行できます。

class Hoge
  def fuga
    p self.class.class_method
  end

    def self.class_method
        'クラスメソッドを実行'
    end
end
hoge = Hoge.new
hoge.fuga #=> クラスメソッドを実行

Q28: Rackについて説明してください

回答:

Rackは、WebサーバーとRailsの間に位置するAPIです。Rackではプラグインを利用できるほか、フレームワークを差し替えたり(RailsSinatraに差し替えるなど)、Webサーバーを差し替えたり(UnicornをPumaに置き換えるなど)できます。

これについては下記YouTube動画を参考にしましたのでその内容をざっくりまとめます。英語の癖が強かったのですがスライドがシンプルで分かりやすかったので理解できました笑

www.youtube.com

Rankとは何かというと、RailsとWebサーバーのやりとりを行う際に利用されるインターフェースのことです。WebサーバーはPumaとかUnicornとかにあたります。Rackは画像のようにRailsとWebサーバーの間に存在し、Rails←→Webサーバー間の通信をサポートします。

https://i.gyazo.com/28b4dd21a0084e5a216f86a0ac383fad.png

Rackを利用するメリット配下のスライドのとおりです。

https://i.gyazo.com/35fe97d4b46443fce3dc64f23422922d.png

  • WebサーバはRailsを知る必要がなく、RailsもまたWebサーバを知る必要がありません。
  • Railsでは、Puma、Think、UnicornなどのRack互換のWebサーバーを使用することができます。
  • Pumaやその他のRubyウェブサーバは、Sinatoraのような複数のフレームワークをサポートしています。

つまりRackが間にあることでWebサーバはRailsに合わせて仕様を変更する必要がなく、RailsもまたWebサーバーに合わせて変更をする必要がないということです。そのため、RackがサポートしているWebサーバーであればRailsはどのWebサーバーも使うことができます。

興味があればGitHubを調べるのもありです!

github.com

Q29: MVCについて説明してください

回答:

MVC(Model-View-Controller)はRailsを構築するソフトウェアデザインパターンです。MVCでは情報の扱いを以下の3つに分割しています。 モデルはデータとロジックを管理します。ビューは情報を表示します。コントローラは入力を受け取り、モデルやビューに渡すデータを準備します。

https://miro.medium.com/max/1232/1*aNHQ7LCCD7hsY6o2cb23HA.png

この理解が本当にRailsでは重要です。1番重要と言ってもいいかもしれません。実は「Q1: ブログアプリで記事のリストを取得するときのリクエスト/レスポンスサイクルをひととおり説明してください」と内容がかなり重なります。

Railsでは情報の扱いを3分割しています。

Model --> データとロジックを管理
Controller --> 入力を受け取り、モデルやビューに渡すデータを準備
View --> 情報を表示

Modelはデータとロジック(ビジネスロジッック)を管理とありますが、データはDBの情報です。ロジックは「Q17:「ファットモデル、薄いコントローラ」の意味を説明してください」でも説明しましたが、Active Recordのメソッドなどの複雑なロジックはコントローラでもビューでもなくモデルに書くということでした。下記の例では投稿の最新5件を取り出すロジックをモデルに書いている例です。

class Post < ApplicationRecord
    scope :recent, -> { order(id: :desc).limit(5) }
end

Controller にはモデルとビューのデータの橋渡しをする役割があります。下記の例ではコントローラー側で上述した最新の投稿5件を取り出すロジックを使用してデータにアクセスしています。

class PostController < ApplicationController
    @recent_posts = Post.recent
    redirect_to posts_path
end

Viewはコントローラーで渡された情報をクライアント側に描画します。

<% @recent_posts.each do |post| %>
<tr>
  <td><%= post.title %></td>
  <td><%= post.content %></td>
</tr>
<% end %>

このようにして情報の描画はViewで、データの情報や取得するロジックはModelで、そしてModelからでたーを取得しViewに表示するデータの受け渡しはControllerで行います。これ何度繰り返し確認しても大事だと思う。

Q30: Rubyのブロックについて説明してください

回答:

Rubyのブロックは、コードを中かっこ{ }または「doとend」で囲んだものです。eachを呼び出すときにはブロックをひとつ渡します。 ブロックには独自のスコープがあり、ブロックの外からアクセスできない独自の変数を1つまたは複数定義できます。ただしブロックの外で定義された変数はブロック内でも変更可能です。

{|x| puts x} # ブロックの例

繰り返し処理とかでよく使用します。下記の例はdoとendで囲んだものです。

[1, 2, 3, 4, 5].each do |i|
   puts i
end

doとendを{}に変更すると下記のようになります。こっちはあんまり使わないかなと思っています。

[1, 2, 3, 4, 5].each { |i|
   puts i
}

「ブロックには独自のスコープがあり、ブロックの外からアクセスできない独自の変数を1つまたは複数定義できます。ただしブロックの外で定義された変数はブロック内でも変更可能です。」とあります。例えば上述したロジックを少し変更してみます。ブロックの内側にあるxは参照できるのですが、外側からxは参照できません。

[1, 2, 3, 4, 5].each do |i|
    x = 2
  puts i * x # => 2,4,6,8,10
end

puts x # 出力されない!

逆にxが外側に定義されていれば内側から外側のxを参照することができます。

x = 2

[1, 2, 3, 4, 5].each do |i|
  puts i * x # => 2,4,6,8,10
end

puts x # => 2

終わりに

今回は総括して重要な内容が多かったと思います。

今回はここまで!!!!

次回はQ31からです!

【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からです!

【Rails】Sorceryを使用したGitHubログイン

はじめに

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

sorceryでGitHubログインを実装しましたので本記事で紹介します。GitHubログインをSorceryで実装している記事はほとんどなかったので誰かの役に立てば幸いです。

公式の流れに基本的には沿っていますがGitHubの例ではないです。

GitHubにアプリ登録

外部認証を使用するためにGitHub側で必要な設定を行います。

① 右上のアイコン → 「Settings」をクリック

https://i.gyazo.com/619fe057684c5f24adb85b767fdec29e.png

② 左ペインから「Developer settings」をクリック

https://i.gyazo.com/727b89bd859cfb92c8bf3c428907ad27.png

③ 左ペインから「OAuth Apps」を選択し、「New GitHub App」をクリックします。

https://i.gyazo.com/3a8f21f2318f28109a49e3a96caf6efd.gif

④ 以下のように各項目を入力し、「Register application」をクリック

※ 開発環境ではlocalhost:3000ですが、デプロイ をした後はドメイン名に変更します。

https://i.gyazo.com/62901d0c6b338c33811a0b426d42beaf.png

⑤ ④の後にアプリの設定画面に遷移します。「You need a client secret to authenticate as the application to the API」(アプリケーション側でAPIを認証するためにはクライアントシークレットが必要)とあるように、client secretがないとGitHubログインが実装できません。「Generate a new client secret」をクリックします。

https://i.gyazo.com/2091128228675fef7f08107ab2cb87aa.png

⑥ Client IDとClient Secretを控える(後ほどアプリ内で記述します。)

https://i.gyazo.com/13ad5aa0ce9d69892585275d6704858d.png

補足

SNSログイン等の外部認証機能はこのように基本的に「新規アプリ作成」というフローが必要です。そこで使われる用語の意味はほとんど共通ですので下記に概要を書いておきます。

  • Authorization callback URL・・・認証後にアクセスするURLのこと。例えば、GitHub認証画面で認証を許可した後のURLに該当する。sorceryの場合は「ドメイン/oauth/callback?provider=(facebook or github or etc...)」で指定します。
  • Client ID & Client Secret・・・外部サイトのアプリ固有の鍵みたいなもの。APIを使用するためにアプリ内で環境変数として指定し、APIとの連携を許可する役割を持っています。

Sorcery側の実装

エディタでGitHub認証するための設定をしていきます。前提として、Sorceryでユーザの通常ログインは実装済みで話を進めていきます。

① authenticationsテーブルを作成

下記コマンドを実行することで外部認証用のテーブルを作成します。

$ rails g sorcery:install external --only-submodules
        gsub  config/initializers/sorcery.rb
      insert  app/models/user.rb
      create  db/migrate/2021xxxxxxxxxx_sorcery_external.rb

下記ファイルが作成されます。

class SorceryExternal < ActiveRecord::Migration[6.1]
  def change
    create_table :authentications do |t|
      t.integer :user_id, null: false
      t.string :provider, :uid, null: false

      t.timestamps              null: false
    end

    add_index :authentications, [:provider, :uid]
  end
end

そのままマイグレーション

$ rails db:migrate

② Authenticationモデルの作成

①で実行したコマンドでテーブルは作成されるのですが、それに対応したモデルは作成されていません。そのため手動で作成する必要があります。

$ rails g model Authentication --migration=false

マイグレーションは実行済みなのでオプションで--migration=falseとしておきます。①でモデルも一緒に作成しておけばオプションの考慮はいらないはずなのですが、、、

AuthenticationモデルとUserモデルにアソシエーションを追加します。

Userモデル

class User < ApplicationRecord
  authenticates_with_sorcery!
  has_many :authentications, dependent: :destroy
  accepts_nested_attributes_for :authentications
end

Authenticationモデル

class Authentication < ApplicationRecord
  belongs_to :user
end

ちなみにaccepts_nested_attributes_forですが、これはそれに属するインスタンスが作成された時に、子モデルも同時に作成する設定です。Userモデルが作成されたら、自動でAuthenticationモデルも作成してくれるということになります。

※ ちなみにaccepts_nested_attributes_forは非推奨となっており、Railsの開発者たちから嫌われています。


③ credentials.yml.encを編集

先ほど控えたClient IDとClient Secretを環境変数としてcredentials.yml.encに記述しアプリ内に登録します。credentials.yml.encは暗号化された文字列が表示されているファイルです。

p2cImUehvb1o9HfMqvKxj0PImkV8nJO12hwcUwC9xuZg6WhKIfkt+yzKIMOs61DflHTnft5abRrDvVDHxwTEe5YQHG+/TdvWF3VlFVxaiG3AhwoiOZqpVmGBVeIk+Bk4cAdNzD6hKwCzoZLC3R80aMtmSDSgGplAp+M3gWvWKPRUUCWnDZD9K/+ayHeuVjf/jmQMwBvHH2aEzDeoFjbaEp5cZgIsVRUnA4HpmAiTHkBmQlN16FWVXB1BmKXfJkCFG01iFUkLMfnUG5sKGHTzb33XRQ0y05pa5RDUQCEZLIuwLL5mL6xLsIs28+P64qLsqqiMfAcVUfQdZMDBSlzH1a+R3cBowF2Zpe9b5gKOcIheM00Xv04imOCxfN6j5Z4waB1u1psOhU/ucUdaN2UrglnVAklHTRC9BjKS6//tfzUsJarpjGT4v7o30MFgkvj825Yjysb0mYQ7FcgcRdZTqDVecz6JQTCAif7+QTbXilPtD5ZbSB7G7Y4GzHP8XaEfXmWPWv15PxotjXVWL+2qI7/2Qy9lJK6WZZU5WfP/a9fX6C5wxy0ZCZlZENamIl6hIlwIcbS5FUurXDZB8WqEGh4v0TYCdzOT9Gskj2Kv+1hQFVsxNdtPig==--6dulaUT3AgkwOLTZ--DD7AmvexctmdANexHgISBw==

しかし、master.keyがconfig配下にある状態で下記コマンドを実行すると中身を編集できます。

$ EDITOR="vi" bin/rails credentials:edit

以下のように編集してください。

github:
  key: 控えたClient ID
  secret: 控えたClient Secret
  callback_url: 'http://localhost:3000/oauth/callback?provider=github

編集後保存します。上述した中身を編集できるコマンドを実行して保存されたか確認しておきましょう。

$ EDITOR="vi" bin/rails credentials:edit

④ sorcery.rbを編集

sorcery.rbをGitHub認証用に対応するように編集します。

Rails.application.config.sorcery.submodules = [:external] # コメントアウト
・
・
・
# Here you can configure each submodule's features.
Rails.application.config.sorcery.configure do |config|
・
・
・# 外部認証にgithubを指定
    config.external_providers = [:github]
・
・
・
    # ③で設定したcredentials.yml.encの値に合わせる
    config.github.key = Rails.application.credentials.dig(:github, :key)
  config.github.secret = Rails.application.credentials.dig(:github, :secret)
  config.github.callback_url = Rails.application.credentials.dig(:github, :callback_url)
  config.github.user_info_mapping = {email: "email", name: "login", remote_avatar_url: "avatar_url"}
  config.github.scope = "user:email"

        # --- user config ---
  config.user_config do |user|

        # -- external --
    # Class which holds the various external provider data for this user.
    # Default: `nil`
    #
    user.authentications_class = Authentication # コメントアウトしてAuthenticationモデルを指定

  end
end

⑤ OauthsControllerを作成して下記のように記述

class OauthsController < ApplicationController
  skip_before_action :require_login, raise: false

  def oauth
    return redirect_to root_path, info: t('defaults.info') if logged_in?
    login_at(params[:provider])
  end

  def callback
    provider = params[:provider]
    if @user = login_from(provider)
      redirect_to root_path, success: "#{provider.titleize}アカウントでログインしました。"
    else
      begin
        @user = create_from(provider)
        reset_session
        auto_login(@user)
        redirect_to root_path, success: "#{provider.titleize}アカウントでログインしました。"
      rescue
        redirect_to root_path, error: "#{provider.titleize}アカウントでのログインに失敗しました。"
      end
    end
  end
end

補足

  • login_from・・・既にユーザが外部認証済みでDBに登録されているか確認し、登録されていればログインをしtrueを返却するメソッド。登録されていなければfalseを返す。
  • create_from・・・login_fromfalseの場合にユーザ登録をするメソッド。

⑥ ルーティングを設定

sorceryではどの外部認証を使っても下記のルーティングを記載します。

# routes.rb
post "oauth/callback", to: "oauths#callback"
get "oauth/callback", to: "oauths#callback"
get "oauth/:provider", to: "oauths#oauth", as: :auth_at_provider

⑦ Userテーブルにremote_avatar_urlカラムを追加

GitHub認証の返却値として、name, email, remote_avatar_urlの値が返却されます。sorceryのGitHub認証では、Userテーブルにこれらのカラムがあることを期待して動作するため、もしremote_avatar_urlがないとエラーを起こします。

undefined method 'remote_avatar_url' for < #class User ・・・>

既にsorceryでUserテーブルを作成していますが、そのカラムにremote_avatar_urlがないため、カラムを追加します。

class AddAvatarsToUsers < ActiveRecord::Migration[6.1]
  def change
    add_column :users, :remote_avatar_url, :string
  end
end
$ rails db:migrate

⑧ リンクをviewに追加

<%= link_to auth_at_provider_path(provider: :github), class: "btn btn-light" do %>
  <i class="fab fa-github mr-2"></i>
    <span class="has-text-weight-medium">Sign in with Github</span>
<% end %>

終わりに

Slackログインには1ヶ月もかかったのでGitHubもそれなりに時間がかかるのかなと思っていましたが、たったの2時間で実装できてしまいました、、、、全然難易度違う、、、、、。しかし、Slackログインを経験したからこそClient IDの流れやCallback URLの設定の理解があったので楽に実装できたんだと思います!

他の外部認証もどんどん試していきたいです!

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

はじめに

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

(前回の記事)

sakitadaiki.hatenablog.com

本記事ではQ13 ~ Q18を深掘りします。

Q13: コールバックとは何かを説明してください

回答:

コールバック(callback)は誤解を招きがちな用語です(訳注: 英語圏では電話の「折り返し」を想像させるためと思われます)。コールバックは、オブジェクトのライフサイクルの中でメソッドを実行するフックを指します。 before_validationafter_saveafter_destroyなど、オブジェクトの作成、更新、削除などに多くのコールバックが存在します。 コールバックは、たとえばUserレコードが作成されたときに、それに関連付けられているContactレコードを作成するといった条件付けのロジックを記述するのに有用です。

オブジェクトのライフサイクルとは、オブジェクトの状態が切り替わるサイクルのことです。例えば、Userクラスのインスタンスが作成される時、そのモデルは作られた状態に切り替わります。

User.create(name: "daiki", email: "hogehoge@sample.com")

上記のユーザを更新する時もそのインスタンスの状態が切り替わります。

User.find_by(email: "hogehoge@sample.com").update(email: "fugafuga@sample.com")

インスタンスを削除する時も削除された状態に切り替わります。

User.find_by(email: "hogehoge@sample.com").destroy

このようにオブジェクトは一度作成されてから削除されるまで状態の切り替わるタイミングがあります。これがオブジェクトのライフサイクルです。

そして、オブジェクトの状態が切り替わるタイミングの前後にイベントを発生させるフックをコールバックといいます。例えば、作成される前に呼び出すコールバックはbefore_createとなります。

例えば、ユーザーが作成された時にクラスにbefore_createを定義していたとします。

class User < ApplicationRecord
    before_create :hello
    
    def hello
        p "hello"
    end
end

すると、インスタンスが作成される前に文字列「hello」が出力されます。このようにオブジェクトのインスタンスの状態が切り替わるイベントの前後(before or after)のタイミングで実行したいメソッドを指定することでフックできます。

※ コールバック一覧は下記に書いておきます。

  • after_initialize・・・オブジェクトがインスタンス化された
  • before_validation・・・バリデーションが行われる直前
  • after_validation・・・バリデーションが行われた直後
  • before_save・・・オブジェクトがDBに保存される直前
  • before_create・・・オブジェクトがDBに新規保存される直前
  • before_update・・・オブジェクトによりDBを更新(UPDATE)する直前
  • after_create・・・オブジェクトがDBに新規保存(INSERT)された直後
  • after_update・・・オブジェクトによりDBを更新(UPDATE)した直後
  • after_save・・・オブジェクトをDBに保存した直後
  • before_destroy・・・destroyメソッドで削除される直前
  • after_destroy・・・destroyメソッドで削除された直後

ちなみに回答の「Userレコードが作成されたときに、それに関連付けられているContactレコードを作成する」ですが、これもわかりやすい例で説明します。例えば、emailがUserではなくContactのカラムだとします。そしてUser has_one Contactの関連付けがあるとします。

class User < ApplicationRecord
    has_one :contact, dependent: :destroy
end
class Cotanct < ApplicationRecord
    belongs_to :user
end

そして、Userのインスタンスが作成された時にそのインスタンスのContactも同時に作成したいとします。その場合、ユーザが作成された後にContactが作成されるようにします。

class User < ApplicationRecord
    has_one :contact, dependent: :destroy

    after_create do # ブロックにして実行したい処理を直接書くこともできる
    create_contact # has_oneのアソシエーションの時に使えるヘルパーメソッド
  end
end

これでUser作成時にContactのレコードも同時に作成できるようになります。

Q14: before_saveコールバックとafter_saveコールバックの使い分けについて説明してください

回答:

あるオブジェクトがsaveされた後に更新をかける場合、更新を永続化させるために追加のデータベーストランザクションが必要になります。つまり、あるオブジェクトの「属性」を更新する場合はbefore_saveコールバックの方が効率的です。 しかし、オブジェクトを保存するまでは存在しない情報もあります(idなど)。つまり、関連付けられたレコードの作成にidが必要な場合は、after_saveコールバックを実行しなければならないでしょう。

まずbefore_saveafter_saveについて詳しく説明します。


**before_save**

バリデーションに成功し、実際にオブジェクトが保存される直前で実行されます。つまり、after_validationの後です。オブジェクトが保存される時なので、オブジェクトが作成されるタイミングとオブジェクトが更新されるタイミングで実行されます。つまり、、、

before_save = before_create(作成前) + before_update(更新前)

after_save

after_update の直後、データベースへの COMMIT の直前に実行されます。回答の説明にある通り、関連オブジェクトを操作する際に使われます。


上記の説明を踏まえた上で解答を解説します。

まずafter_saveについては問題ないでしょう。オブジェクトが保存される前までは対象のオブジェクトにはないデータが存在します。それらを使う場合はafter_saveの方がコールバックの使い方としては正しいでしょう。下記は保存されたidを使用した処理ですが、before_saveの場合は値がないのでエラーになります。

class User < ApplicationRecord
    after_save do
    p id # 保存されたidを使用した処理
  end
end

class User < ApplicationRecord
    before_save do
    p id # 作成時にidがないのでエラーとなる
  end
end

before_saveはどうでしょうか。

「あるオブジェクトがsaveされた後に更新をかける場合、更新を永続化させるために追加のデータベーストランザクションが必要」とありますがこれはどういうことでしょうか。

ここでいう”永続化”ですが、これはオブジェクトの永続化とGoogle先生に聞けば何度なくの概要で出てきました。

永続化とは、インスタンスの状態を半永久的に保存し、いつでも復元できるようにすることです

https://www.atmarkit.co.jp/fdb/rensai/javapersis01/javapersis01_2.html

しかし、Railsではbefore_saveを使わずともインスタンスの状態を更新できます。なので、更新を永続化させるために追加のデータベーストランザクションが必要ないような気がします、、、。はて、自分の理解が足りていないのでしょうか、、、?誰か解答を持っていれば解説お願いします💦

自分個人の回答ですが、before_saveについてはafter_saveに当てはならない、つまり、保存された後のデータを必要としない場合に使用する、で問題ないと思っています。

Q15: Railsの「イニシャライザ」について説明してください

回答:

イニシャライザには、アプリの起動時にのみ実行する設定ロジックを置きます。つまり、イニシャライザの内容を変更した場合はRailsサーバーの再起動が必要です。イニシャライザは/config/initializers/ディレクトリの下に置かれます。

これは回答の説明で十分かなと思っています。

一応深掘りすると、イニシャライザは/config/initializers/ディレクトリ配下に置かれる設定ファイル群のことです。

https://i.gyazo.com/b8bfc0b1a1ffa3bedcea752c6b8f76d0.png

基本的にRailsはサーバが起動している状態でファイルを編集しても再起動することなく編集箇所が反映されます。例えば、app/ディレクトリ配下のコントローラーやモデル、ビューなどがそうです。

しかし、それとは異なりサーバー起動時にのみ読み込む設定ファイルがあり、それがイニシャライザです。イニシャライザのファイルには例えばgemの設定ファイルなどが置かれます。以下はsorcery.rbの中身の一部です。

Rails.application.config.sorcery.submodules = []

# Here you can configure each submodule's features.
Rails.application.config.sorcery.configure do |config|
・
・
・
・
end

イニシャライザのファイルを編集したら必ずサーバーを再起動するように心がけましょう。そうしないと意図した通りにアプリが動かないと思います。

Q16: deleteとdestroyの違いを説明してください

回答:

delete・・・レコードを1件削除する destroy・・・レコードを1件削除し、コールバックを実行する Railsアプリのモデルファイル関連付けで最もよく使われるのはdestroyコールバックです。たとえば、以下のコードはarticleがdestroyされると関連するcommentsもdestroyされます。

class Article < < BaseController
  has_many :comments, dependent: :destroy
end

まずはdestroyから。上述のようにdestroyで最もよく使われるのはコールバックdestroyだと思います。dependent: :destroyとすることで、インスタンスが削除された時に関連モデルも全て削除されるように設定できます。

Article.create(title: "hogehoge", body: "fugafuga")
Article.last.comments.create(text: "xxxxxxxx") # コメントを1つ作成
Article.last.destroy # コメントが全て削除される

これはRubyインスタンスでライフサイクルにdestroyがあるためですね。なのでdestroy前後のコールバックを呼び出すことができます。

before_destroy
after_destroy

次にdeleteですが、deleteはインスタンスのライフサイクルにないので、コールバックを実行することなくレコードの削除のみ実行します。よって、destroyのように関連付けモデルに対してはレコードの削除を行えないということになります。

関連モデルを削除できない理由はdeleteはActiveRecordを介さないと説明しています。対象のレコードにSQLであるDELETEを直接実行している感じですかね。

Q17:「ファットモデル、薄いコントローラ」の意味を説明してください

回答:

ビジネスロジックはコントローラではなくモデルに配置すべきです。そうすることでロジックの単体テストが行いやすくなり、再利用性も向上します。コントローラは、ビューとモデルの間で情報を受け渡しするための場でしかありません。これはあくまで新人Rails開発者向けの一般的なアドバイスであり、特に巨大なアプリでは実際には推奨されていません。 訳注: 巨大なアプリでは、モデルやコントローラ以外のロジックの置き場所を別途定めるのが普通です。

Skinny Controller, Fat Model」(=薄いコントローラ、ファットモデル)という2006年の記事があったそうですが、どうやらそれが由来のようです。

Buckblog: Skinny Controller, Fat Model

ビジネスロジックはコントローラではなくモデルに配置すべき」とあるように、コントローラーにはシンプルな処理のみ担当させ、その他複雑なロジックはモデルが担当すべきというものです。ActiveRecordの複雑なロジックはモデル側に記述すべきであり、コントローラーはモデルとビューの間でデータの受け渡しを行うためだけの場所に過ぎません。

「ファットモデル、薄いコントローラ」を成り立たせるために1番に連想されるのはscopeだと思います。

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

scopeはActiveRecordを切り出してモデルに記述することで、コントローラーから取り出したいデータをscopeを介して取得するメソッドを定義できます。

def index
    @notes = Note.search("hogehoge")
end

もしscopeを使わないのであればコントローラに冗長なActiveRecordのロジックを記述しなければなりません。

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

結果コントローラの中の記述量が多くなってしまいます。それを回避するためにscopeがあるわけです。

しかし、「新人Rails開発者向けの一般的なアドバイスであり、特に巨大なアプリでは実際には推奨されていません。」とあるように何でもかんでもロジックをモデルに移せばいいというわけでもなさそうですね。。。ロジックの置き場所をモジュールで切り出す方法もありますし、一概にも「ファットモデル、薄いコントローラ」がいつも正しくなるということではないんですね。これは次の問でより詳しく解説します。

Q18:「薄いコントローラ、薄いモデル」の意味を説明してください

回答:

コードベースが成長するに連れて、ファットモデルが手に負えなくなり、モデルの責務が過剰になって管理不能に陥ってしまいます。モデルは永続化に専念し、モデル内のロジックを肥大化させないようにすべきです。「単一責任の原則」を常に意識し、ロジックをモデルから他のデザインパターン(Service Objectなど)に追い出すことで、モデルをもっと薄くできます。

Q17とはまた違った回答にですね。「Skinny Controller, Skinny Model」(=薄いコントローラ、薄いモデル)とあるようにどちらにも記述量を最小限に留めるということでしょうか。

Rails: skinny controller, skinny model

まずこれはオブジェクト指向の考え方になるのですが、「クラスには単一責任のみを課す」というそもそもの考えがあります。クラスごとに責任を徹底させることでコード変更の際に変更するコード量を可能な限り少なくするのが理想的なコードの状態です。

モデルも当然クラスです。もしファットモデルを許容するのであれば、そのモデルは複数の責任が課されてしまうためオブジェクト指向的にもよくないでしょう。自分もポートフォリオでUserモデルに100行以上のコードを記述しましたが、個人開発で100行となるとより大きなサービスだと確かに手に負えなくなりそうです。上記の記事ではapp/libディレクトリ配下にロジックごとにクラスを作成しコントローラー側で呼び出しています。

# app/controllers/users_controller.rb
# total: 20 lines (from 20 lines)
class UsersController < ApplicationController
  def create
    user = User.create!(user_params)
    BusinessLogicA.new
    BusinessLogicB.new
    render json: { status: "OK", message: "User created!" }, status: 201
  end

  def update
    user = User.update!(user_params)
    BusinessLogicB.new
    render json: { status: "OK", message: "User updated!" }, status: 200
  end
  ...
end

# app/controllers/companies_controller.rb
# total: 21 lines (from 21 lines)
class CompaniesController < ApplicationController
  def create
    company = Company.create!(company_params)
    BusinessLogicC.new
    BusinessLogicD.new
    render json: { status: "OK", message: "Company created!" }, status: 201
  end

  def update
    company = Company.create!(company_params)
    BusinessLogicC.new
    BusinessLogicE.new
    render json: { status: "OK", message: "Company updated!" }, status: 200
  end
  ...
end

# app/models/user.rb
# total: 3 lines (from 24 lines)
class User < ApplicationRecord
  has_secure_password
end

# app/models/company.rb
# total: 2 lines (from 53 lines)
class Company < ApplicationRecord
end

# app/lib/business_logic_a.rb
class BusinessLogicA
  # 10 lines of BusinessLogic-A
end

# app/lib/business_logic_b.rb
class BusinessLogicB
  # 5 lines of BusinessLogic-B
end

# app/lib/business_logic_c.rb
class BusinessLogicC
  # 20 lines of BusinessLogic-C
end

# app/lib/business_logic_d.rb
class BusinessLogicD
  # 14 lines of BusinessLogic-D
end

# app/lib/business_logic_e.rb
class BusinessLogicE
  # 9 lines of BusinessLogic-E
end

これはオブジェクト指向的な考え方をまさに取り入れていると言えるでしょう。「単一責任の原則」の通り、各クラスは単一の責任を保っているのでコードを変更する時はその一つのファイルを編集するだけですみそうですし。

終わりに

今回はここまで!!!!

次回はQ19からです!

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

はじめに

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

(前回の記事) sakitadaiki.hatenablog.com

本記事ではQ7 ~ Q12を深掘りします。(Q9については後述しますが割愛させてください💦 )

Q7: Gemfileについて説明してください

この質問の答えはすぐには思いつきませんでした。普段何気なく使いたいgemを記述してbundle installする。そしたら使いたいgemの機能が使えるようになる。この程度の理解だったのでこの際深掘りしてみます。

RubyGems

まず私たちが普段使用しているgemは、RubyGemsというパッケージマネージャーのもとに成立しています。

RubyGemsは、プログラミング言語Rubyのためのパッケージマネージャで、Rubyのプログラムやライブラリを「gem」と呼ばれる自己完結型の形式で配布するための標準的なフォーマットと、gemのインストールを容易に管理するためのツール、およびgemを配布するためのサーバを提供します。

RubyGems - Wikipedia

RubyGemsというパッケージマネージャーがgemを管理しインストール方法を提供するため、個人でgemを開発しそれを配布することで私たちはインストールすることができます。

gem

ここでgemについても説明します。gemとはライブラリのことを指します。ではライブラリとは何でしょうか。色々と調べる中で以下の説明が1番しっくりきました。

汎用性の高い機能を他のプログラムで呼び出して使えるように部品化して集めたファイル

https://i.gyazo.com/db169baa54f3830fa52db0a5ba18f036.png

gemの仕組みを図解形式で学ぼう

ログイン機能やファイルストレージ機能は自作で実装しようとすると大変手間です。そういった手間を汎用性の高い機能を部品化してあるファイル(ライブラリ)をインストールすることで誰でもその機能が利用できるということです。

Bundler

bundle installとコマンドを打つことでgemをインストールするという理解でしたが、Bundlerもgemの一つです。そういえばインストールするときにgemをインストールする時と同じ方法でインストールしてましたね。

gem install bundler

Bundlerの役割は下記のとおりです。

bundlerとは、gemのバージョンやgemの依存関係を管理してくれるgemです。bundlerを使うことで、複数人での開発やgemのバージョンが上がってもエラーを起こさずに開発できます。

インストールする役割というよりたくさんのgemが存在する中でそれぞれのgemの依存関係を管理しているのですね。

ちなみにbundlerの公式サイトがありました。個人gemではないのでgithubでの管理ではないですね。

Bundler: The best way to manage a Ruby application's gems

Gemfle

ではGemfileの役割は一体何なのでしょうか?記事の回答は以下のとおりですが、言葉足らずなのかあまり理解できません。

ひとつのRubyアプリケーションで利用される依存関係を指定します。これはプロジェクトのルートディレクトリに置かれます。

「これはプロジェクトのルートディレクトリに置かれます」の部分についてですが、bundlerはインストールする際にルートディレクトリにGemfileがあることを想定しているので、絶対にルートディレクトリに置かれる必要があるようです。

「ひとつのRubyアプリケーションで利用される依存関係を指定します」の部分ですが、これは果たして正解なのでしょうか?

Gemfileは依存関係というよりはインストールしたいライブラリを記述するためだけの役割のような気がしてないません。なぜなら、私たちは依存関係をあまり気にせずにGemfileを手動で記述しています。

source 'https://rubygems.org'
gem 'nokogiri'
gem 'rack', '~> 2.0.1'
gem 'rspec'

Gemfileとは?と聞かれたら、依存関係というよりは自分が使いたいgem一覧を記述するためのファイルといっても正解になるのではないでしょうか?そしてBundlerがGemfileを読み込みまだインストールしていないgemがあればインストールするといった流れかと思います。

Q8: Gemfile.lockについて説明してください

Q7ではGemfileが依存関係を指定しているという記述でしたが、どちらかというとGemfile.lockが依存関係に対してアプローチしているように思えます。指定というよりは依存関係を記録するのがGemfile.lockの役割です。

Gemfile.lockには依存関係にあるgemも含め、bundlerによってインストールされた全てのgemとそのgemのバージョンが記載されています。

【初心者向け】bundler、Gemfile、Gemfile.lockの関係性について図でまとめてみた - Qiita

インストールしたgemを全て記録しておいてくれるため、他の開発者が同じようにbundle installする時にGemfile.lockを頼りに同様のバージョンのgem群をインストールしてくれるということですね。もし仮にGemfile.lockがなければGemfileでバージョン指定をしていない限り、bundle installしたときの最新バージョンのgemがインストールされてしまい、開発者ごとにgemのバージョンが違うということが起きてしまいます。

  • Gemfile

bundlerでインストールするgemを記載する

  • Gemfile.lock

bundlerでインストールしたgemを記載する

Q9: Railsでどんなデザインパターンを使ったことがありますか?

以下が記事の回答です。

Railsには、Service Objectパターン、Value Objectパターン、Form Objectパターン、Query Objectパターン、View Objectパターン、Policy Objectパターン、Decoratorパターンといった多くのデザインパターンがあります。 各デザインパターンとコード例については、以下のチュートリアル記事に詳しく載っています。

??????????????????

こちらの深掘りですが、かなり記事の内容が多くなってしまうため割愛させてください🙇‍♂️

下記リンクの記事を見てくだされば大体把握できるかと思います!

applis.io

Q10: Railsではデータベースのステートをどのように管理するか説明してください

開発者は手動でマイグレーションファイルを生成し、そこに指示を追加します。 これらのマイグレーションファイルは、Active Recordに対して既存のデータベースのステートの「変更方法」を指示します。そのために、過去のマイグレーションファイルを削除または変更するとデータベースのステートに悪影響を及ぼす可能性があり、おすすめできません。

ステートはつまり状態のことです。データベースの状態をどのように管理するか説明するということですね。

Railsではマイグレーションファイルでデータベースを管理しています。例えば、データベースにUserのデータを入れたいときは下記コマンドでマイグレーションファイルを作成します。

rails g model user email:string name:string

作成されたマイグレーションファイルを少し編集したのが下記のもの

# 20XXXXXXXXXXXX_user.rb
class CreateUsers < ActiveRecord::Migration[6.1]
  def change
    create_table :users do |t|
      t.string :email,            null: false
      t.string :name, null: false

      t.timestamps                null: false
    end

    add_index :users, :email, unique: true
  end
end

これをデータベースに反映させるのが下記コマンド

rails db:migrate

注目すべきはまったくDB本来のコマンドを使用していない点です。CREATE TABLE ~~みたいなコマンドを本来はDBにアクセスして実行する必要がありますが、Railsはそれを意識する必要がありません。

ここで実際にマイグレーションファイルが行っていることが、Active Recordに対して既存のデータベースのステートの「変更方法」の指示です。マイグレーションファイルはデータベースの変更指示文という理解の方が正しく思えます。

「過去のマイグレーションファイルを削除または変更するとデータベースのステートに悪影響を及ぼす可能性があり、おすすめできません。」という箇所ですが、例えばリリース後にユーザテーブルにroleカラムを追加するとします。しかし、この時にロールバックを行ってからマイグレーションファイルを変更して再度マイグレーションするのは非推奨ということです。

一度ロールバック

rails db:rollback

マイグレーションファイルを編集

# 20XXXXXXXXXXXX_user.rb
class CreateUsers < ActiveRecord::Migration[6.1]
  def change
    create_table :users do |t|
      t.string :email,            null: false
      t.string :name, null: false
            t.integer :role, null: false, default: 0

      t.timestamps                null: false
    end

    add_index :users, :email, unique: true
  end
end

再度マイグレーション

rails db:migrate

これは一度変更を指示したマイグレーションファイルを一度取り消して、違う指示をするマイグレーションファイルを反映させています。一見問題ないように見えますが、一度指示した物を取り消しているので、既にデータとして保存されているUserの存在もなかったことにされてしまいます。つまり、保存されているデータが全て消えるということですね。

なので、特にリリース後に設計を変更するのであれば、変更をし直すのではなく、新しいマイグレーションファイルを作成しさらに変更指示を重ねることが推奨されるということです。

rails generate migration AddRoleToUsers role:integer
class AddRolesToUsers < ActiveRecord::Migration
  def change
    add_column :users, :role, :integer

  end
end

質問の答えから話が脱線してしまいましたが、答えとしては、

RailsはデータベースのステートをマイグレーションファイルというDBへの指示書を介して管理する、です。

Q11: countとlengthとsizeの違いを説明してください

記事の回答は以下のとおり

countメソッド ⇒ レコードの件数をカウントするSQLクエリを実行します。これはDBとメモリでレコード数が違う可能性がある場合に有用です。 lengthメソッド ⇒ メモリ上のコレクションに含まれるアイテムの件数を返します。データベーストランザクションを実行しない分、countより高速です。lengthは文字列の文字数をカウントするときにも使われます。 sizeメソッド ⇒ これはlengthエイリアスなので動作はlengthと同じです。

もうちょい深掘りします。

countメソッド

レコードの件数をカウントするSQLクエリを実行とあります。実際に調べてみました。

User.all.count
   (0.8ms)  SELECT COUNT(*) FROM `users`
=> 3

COUNT(*)(レコードの件数をカウントするSQLクエリ)を実行しています。

lengthメソッド

メモリ上のコレクションに含まれるアイテムの件数を返却するとあります。

User.all.length
  User Load (0.6ms)  SELECT `users`.* FROM `users`
=> 3

COUNT(*)は実行せず直接行数を取得しています。0.6msと確かにcountより高速に見えます。

文字数のカウントもできます。

"aaaaaa".length
=> 6

sizeメソッド

lengthエイリアス

User.all.size
   (0.3ms)  SELECT COUNT(*) FROM `users`
=> 3
"aaaaaa".size
=> 6

size/lengthの方が高速なのでcountは使う必要がないかと思われるかもですが、countの最大の利点は引数をとれることにあります。用途としては引数の要素がレシーバーにいくつ含まれるかをカウントします。

# 配列(引数あり)
[1, 2, 3].count(3) #=> 1

# 配列(引数なし)
[1, 2, 3].count #=> 3

# 文字列(引数あり)
"aaaaaa".count("a")
=> 6

# 文字列(引数なし)
"aaaaaa".count(a)
NameError: undefined local variable or method `a' for main:Object

これらの違いはあまり意識せずに使用していました。引数をとれるcountはかなり応用が効きそうですね。

Q12: 認可(authorization)をどのように実装しましたか?

認可は、ユーザーの種類に応じて、アプリで許可するアクセスレベルを変更することに関連します。これは、アクセスレベルの異なるユーザータイプがとても多い場合に便利です。

認可(Authorization)というのは とある特定の条件に対して、リソースアクセスの権限を与えること。例えば、一般ユーザは管理者ページにアクセスできませんが、管理者権限があるユーザは管理者ページにアクセスすることができます。

https://i.gyazo.com/cf4371c1d890fb734fc3ec0511bce421.png

認可については過去のブログで自分が紹介していたのである程度は理解済みです。

sakitadaiki.hatenablog.com

認証(Authentication)は通信の相手が誰であるかを確認すること。ログインなどは本人確認のためにメールアドレスとパスワードを入力させますね。これがAuthorizationとは異なる部分ですね。


認可機能の主要なgemは下記の3つかなと思います。Bankenについては少しマイナーですが、使いやすさは1番だと思っています。

  • Pundit

github.com

  • Banken

github.com

  • Cancan

github.com

終わりに

今回はここまで!!!!

次回はQ13からです!

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

はじめに

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

技術面談の対策としてRUNTEQでは以下の記事をおすすめしています。

techracho.bpsinc.jp

53問もありますし充実しています。そして問題を解こうとすると案の定言葉で説明できないことが多かったです。しかし、このサイトの回答自体も意図的か明確に回答を書いていません。所々「この回答で十分なの?」と思うことがありました。そこで何記事かに渡って53問全てを深掘りして回答をここに残したいと思います。就活も進められるし自分の理解も深められるし一石二鳥です!

Q1: ブログアプリで記事のリストを取得するときのリクエスト/レスポンスサイクルをひととおり説明してください

ユーザーが記事のリストを取得するボタンをクリックしたときに/articlesのURLにGETのリクエストを送信します。

その後、routes.rbに割り当てられているそのリクエストに対応するArticlesコントローラーのindexアクションを実行します。

# config/routes.rb
Rails.application.routes.draw do
    get 'articles', to: 'articles#index'
end

indexアクション内でArticle.allが実行されて、Articleモデル経由でDBから記事リストのデータを取り出し、インスタンス変数(@articles)に代入されます。

class ArticlesController < ApplicationController
    def index
        @articles = Article.all
    end
end

最後にビュー側で記事リストが格納されている@articlesを展開しユーザーに表示します。

<!-- app/views/articles/index.html.erb -->
<% @articles.each do |article| %>
<tr>
    <td><%= article.title %></td>
    <td><%= article.content %></td>
</tr>
<% end %>

Q2:「Rubyでは(ほぼ)あらゆるものがオブジェクトである」について説明してください

オブジェクト指向言語におけるオブジェクトとはクラスのインスタンスのことを示すらしいです。これについては一応別の記事も参照して確認しました。

オブジェクトを定義する雛形をクラス(class)、クラスに基づいてプログラム実行時にコンピュータのメモリ上に展開されたオブジェクトのことをインスタンス(instance)と言うが、実際上はインスタンスのことを指してオブジェクトと呼ぶことも多い。

e-words.jp

Rubyにおけるすべてのクラスは最終的にスーパークラスを持たないBasicObjectクラスを継承していることがコンソールで確認できます。

# 数値
1.class
=> Integer
1.class.class
=> Class

# 文字列
"a".class
=> String
i"a".class.class
=> Class
"a".class.superclass
=> Object
"a".class.superclass.superclass
=> BasicObject
"a".class.superclass.superclass.superclass
=> nil

http://railstutorial.jp/images/figures/string_inheritance_ruby_1_9.png

Rails チュートリアル第4章の図

https://railstutorial.jp/chapters/rails-flavored-ruby?version=4.0#sec-a_class_of_our_own

よって、Rubyでは(ほぼ)あらゆるものがオブジェクトであることが言える!となるわけです。

メソッドや条件分のifなどはコンソールで打ち込んでもクラスを継承していないことがわかります。

def destroy
   logout
     redirect_to root_path
   end
end
=> :destroy # destroyメソッドが定義される
destroy.class
=> (irb):37:in `destroy': undefined local variable or method `logout' for main:Object (NameError)

よって"ほぼ"となり全てがオブジェクトというわけではないんですね。


Q3: Rubyの型は静的ですか?動的ですか?

変数の型をいつでも変更できるのが動的型付け言語になります。

a = 2
=> 2
a = "a"
=> "a"

よってRubyは動的型付け言語です。

他の動的型付け言語としては以下が挙げられます。

では静的型付け言語は型を変更できないということになります。正確には変数に代入する前に変数の型を宣言する必要があります。

// Java example
int num; // numはIntegerだよと宣言
num = 5; // numにIntegerである5を代入

他の静的型付け言語としては以下が挙げられます。

深くは調べていないのですが、下記記事を参照してそれぞれのメリットデメリットも引用しておきます。

qiita.com

note.com

動的型付け言語のメリット

  • 記述量が大幅に減る
  • 比較的簡単にプログラムが書ける

よって、小規模なシステム開発や小回りが効くアジャイル開発に向いている

動的型付け言語のメリット

  • コンパイル時にエラーが出てくれる
  • メモリ領域の最適化が行える

よって、大規模なシステム開発などシステム上堅牢性が求められる開発に向いている

Q4: Rubyのゲッターとセッターについて説明してください

この質問が本記事の質問の中で1番難しいと思います。復習の機会になってよかったです。

Rubyではインスタンス変数の値はクラス内からしか取得できないようです。ですので下記のようにクラス外からの呼び出しができていません。

class User 
   def initialize(name)
     @name = name
   end
end
=> :initialize
user = User.new("a")
=> #<User:0x00007f837c1118c0 @name="a">
p user.name
(irb):13:in `<main>': undefined method `name' for #<User:0x00007f837c1118c0 @name="a"> (NoMethodError)

参照するためにはクラス内にインスタンス変数を参照するメソッド「ゲッター」を定義する必要があります。

class User 
   def initialize(name)
     @name = name
   end
 
   def getName 
     @name
   end
end
=> :getName
user = User.new("daiki")
=> #<User:0x00007f837c0a0580 @name="daiki">
p user.getName
"daiki"
=> "daiki"

セッターはインスタンス変数をクラス内で更新するメソッドになります。"="が末尾についたメソッドはセッターメソッドとなります。

class User 
    def initialize(name)
      @name = name
    end

  def getName
    @name
  end
 
  def changeName=(name)
    @name = name
  end
end
=> :changeName=
user = User.new("daiki")
=> #<User:0x00007f837c0e3b00 @name="daiki">
p user.getName
"daiki"
=> "daiki"
user.changeName = "dai-chan" # 値を変更
=> "dai-chan"
p user.getName
"dai-chan"
=> "dai-chan"

Railsを使っていると意識する必要がないところなのですが、理解しておくと応用が効きそうです。とはいうものの使わないから忘れてしまうので、自分は英語の意味で覚えるようにしています。setが少しあやふやですが、、、

get・・・取得する => 値を外部から取得する
set・・・設定(定義)する => 値を外部から更新する

アクセスメソッドを使えば簡易的にゲッターとセッターが定義できます。

attr_reader

ゲッターのみ定義

値の更新はできない

class User
    attr_rader :name  #ゲッター

  def initialize(name)
    @name = name
  end
end

user = User.new("daiki") 
user.name  #=>"daiki"

attr_writer

セッターのみ定義

値の取得ができない

class User
    attr_writer :name  #セッター

  def initialize(name)
    @name = name
  end
end

user = User.new("daiki") 
user.name = "dai-chan"  #=>"dai-chan"

attr_accessor

ゲッターとセッターどちらも定義できる

class User
    attr_accessor :name  #ゲッターとセッター

  def initialize(name)
    @name = name
  end
end

user = User.new("daiki") 
user.name  #=>"daiki"
user.name = "dai-chan"  #=>"dai-chan"

ちなみにRailsではなぜ意識する必要がないのかというと、ActiveRecord::Base を継承したクラスはそのclassが紐づくテーブルのカラムがプロパティ値となり、明示的にgetter or setter を定義する必要がないらしいです。ActiveRecordどんだけ優秀なんだ。

Q5: Rubyであるメソッドを呼び出したときの動作を説明してください

この質問、一見メソッドのことを説明するだけというシンプルな質問に見えますが、よく読むと違います。"動作"を説明する必要があります。

ちなみに質問集のサイトの回答が以下のとおり。

メソッド名を含む1件のメッセージがそのオブジェクトに送信されます。オブジェクトにそのメソッドが存在する場合は、オブジェクトがそのメソッドを呼び出します。以下のようにRubysendメソッドの動作を考えると、この点がよりよく見えてきます。

obj.hello  #=> 'hello'
obj.send(:hello) #=> 'hello'

?????????????

自分は初見では理解できませんでした、、、、。自分の理解力が低いだけかと、、、、。

sendメソッドがまず分からなかったので調べました。sendメソッドとはレシーバの持っているメソッドを呼び出してくれるメソッドのようです。

class User
  def name
    puts "taro"
  end
end

user = User.new
#定義したメソッドを呼び出す
user.name  # => taro

# sendを使った書き方
user.send(:name) # => taro
user.send("name")  # => taro

qiita.com

レシーバーであるuserにnameメソッドが定義されているのでそれを引数にしていることでレシーバーの持っているメソッドを呼び出しています。正直どこで使うのか分からないメソッドでしたが、sendメソッドは理解できました。

ここで下記文章を再掲します。

メソッド名を含む1件のメッセージがそのオブジェクトに送信されます。オブジェクトにそのメソッドが存在する場合は、オブジェクトがそのメソッドを呼び出します。

この文章はsendメソッドの流れそのものを表していることが今なら理解できると思います。つまり、sendメソッドを使用するとレシーバーの中に指定したメソッドがあればそのメソッドを呼び出していますね。

  • user.send(:name)を実行

  • nameメソッドを実行

つまりこれは他のメソッドでも同じように動作しているということが言えます。

  • user.nameを実行

  • nameメソッドを実行

ということですね。これが普段あまり考えずに使用しているメソッドの動作です。オブジェクトの中に存在すれば実行する。だから、メソッドが存在しなければ存在しなければundefined methodのエラーになる訳ですね。

user.hoge
=> undefined method `hoge' for #<User:0x00007fb2b48ac668> (NoMethodError)

undefined methodエラーの意味を再確認しました。

Q6: あるRailsアプリ内のルーティングをすべて表示してください

rake routes、もしくはrails routesですね。1番簡単でした。これ本当に質問されるんですかね。

$ rake routes
                                                                    Prefix Verb   URI Pattern                                                                                       Controller#Action
                                    root GET    /                                                                                                 top#index
                                   login GET    /login(.:format)                                                                                  user_sessions#new
                                         POST   /login(.:format)                                                                                  user_sessions#create
                                  logout DELETE /logout(.:format)                                                                                 user_sessions#destroy
           rails_postmark_inbound_emails POST   /rails/action_mailbox/postmark/inbound_emails(.:format)                                           action_mailbox/ingresses/postmark/inbound_emails#create
              rails_relay_inbound_emails POST   /rails/action_mailbox/relay/inbound_emails(.:format)                                              action_mailbox/ingresses/relay/inbound_emails#create
           rails_sendgrid_inbound_emails POST   /rails/action_mailbox/sendgrid/inbound_emails(.:format)                                           action_mailbox/ingresses/sendgrid/inbound_emails#create
     rails_mandrill_inbound_health_check GET    /rails/action_mailbox/mandrill/inbound_emails(.:format)                                           action_mailbox/ingresses/mandrill/inbound_emails#health_check
           rails_mandrill_inbound_emails POST   /rails/action_mailbox/mandrill/inbound_emails(.:format)                                           action_mailbox/ingresses/mandrill/inbound_emails#create
            rails_mailgun_inbound_emails POST   /rails/action_mailbox/mailgun/inbound_emails/mime(.:format)                                       action_mailbox/ingresses/mailgun/inbound_emails#create
          rails_conductor_inbound_emails GET    /rails/conductor/action_mailbox/inbound_emails(.:format)                                          rails/conductor/action_mailbox/inbound_emails#index
                                         POST   /rails/conductor/action_mailbox/inbound_emails(.:format)                                          rails/conductor/action_mailbox/inbound_emails#create
       new_rails_conductor_inbound_email GET    /rails/conductor/action_mailbox/inbound_emails/new(.:format)                                      rails/conductor/action_mailbox/inbound_emails#new
      edit_rails_conductor_inbound_email GET    /rails/conductor/action_mailbox/inbound_emails/:id/edit(.:format)                                 rails/conductor/action_mailbox/inbound_emails#edit
           rails_conductor_inbound_email GET    /rails/conductor/action_mailbox/inbound_emails/:id(.:format)                                      rails/conductor/action_mailbox/inbound_emails#show
                                         PATCH  /rails/conductor/action_mailbox/inbound_emails/:id(.:format)                                      rails/conductor/action_mailbox/inbound_emails#update
                                         PUT    /rails/conductor/action_mailbox/inbound_emails/:id(.:format)                                      rails/conductor/action_mailbox/inbound_emails#update
                                         DELETE /rails/conductor/action_mailbox/inbound_emails/:id(.:format)                                      rails/conductor/action_mailbox/inbound_emails#destroy
new_rails_conductor_inbound_email_source GET    /rails/conductor/action_mailbox/inbound_emails/sources/new(.:format)                              rails/conductor/action_mailbox/inbound_emails/sources#new
   rails_conductor_inbound_email_sources POST   /rails/conductor/action_mailbox/inbound_emails/sources(.:format)                                  rails/conductor/action_mailbox/inbound_emails/sources#create
   rails_conductor_inbound_email_reroute POST   /rails/conductor/action_mailbox/:inbound_email_id/reroute(.:format)                               rails/conductor/action_mailbox/reroutes#create
                      rails_service_blob GET    /rails/active_storage/blobs/redirect/:signed_id/*filename(.:format)                               active_storage/blobs/redirect#show
                rails_service_blob_proxy GET    /rails/active_storage/blobs/proxy/:signed_id/*filename(.:format)                                  active_storage/blobs/proxy#show
                                         GET    /rails/active_storage/blobs/:signed_id/*filename(.:format)                                        active_storage/blobs/redirect#show
               rails_blob_representation GET    /rails/active_storage/representations/redirect/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations/redirect#show
         rails_blob_representation_proxy GET    /rails/active_storage/representations/proxy/:signed_blob_id/:variation_key/*filename(.:format)    active_storage/representations/proxy#show
                                         GET    /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format)          active_storage/representations/redirect#show
                      rails_disk_service GET    /rails/active_storage/disk/:encoded_key/*filename(.:format)                                       active_storage/disk#show
               update_rails_disk_service PUT    /rails/active_storage/disk/:encoded_token(.:format)                                               active_storage/disk#update
                    rails_direct_uploads POST   /rails/active_storage/direct_uploads(.:format)

終わりに

今回はここまで!!!!

次回はQ7からです!

【Nuxt】NuxtでAxios

はじめに

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

今回はNuxt.jsでAxiosを使用した簡単なチュートリアルを記述します。

API取得サイト

AxiosはJSONレスポンスを受け取るのでJSONレスポンスを返却するサイトが必要です。

今回はAPIサーバを自分で設けず、JSONのレスポンスを返してくれる以下のサイトを利用します。

JSONPlaceholder

試しにブラウザ上で以下のURLにアクセスしてみましょう。

https://jsonplaceholder.typicode.com/users

すると以下のようにJSONレスポンスを返してくれます。

https://i.gyazo.com/3bfe38ff51825dfb8a59d38b6bb239d0.png

導入

まずはプロジェクトを立ち上げます。プロジェクトの設定ですが、下記記事と同じ設定にしておりますのでご参照ください。

【Nuxt】プロジェクト立ち上げ - 大ちゃんの駆け出し技術ブログ

プロジェクトを作成したら下記コマンドでaxiosをインストールします。

$ yarn add axios

ユーザ一覧ページ

ユーザ一覧ページを実装します。せっかくvuetifyを使用していうのでv-listタグを使用してユーザ一覧を表示します。

まずusersディレクトリをpages配下に作成します。その後usersディレクトリ配下にlist.vueを作成し、以下のように記述しましょう。

<!-- pages/users/list.vue -->
<template>
  <v-list three-line>
    <template v-for="user in users">
      <v-list-item :key="user.id" nuxt>
        <v-list-item-content>
          <v-list-item-title v-html="user.name"></v-list-item-title>
          <v-list-item-subtitle v-html="user.email"></v-list-item-subtitle>
        </v-list-item-content>
      </v-list-item>
    </template>
  </v-list>
</template>

<script>
import axios from "axios";

export default {
  data() {
    return {
      users: []
    };
  },
  mounted() {
    axios
      .get("https://jsonplaceholder.typicode.com/users")
      .then(response => (this.users = response.data));
  }
};
</script>

mounted時にユーザ一覧を返すJSONをGETでリクエストし、レスポンスをusersに格納しています。

mounted() {
  axios
    .get("https://jsonplaceholder.typicode.com/users")
    .then(response => (this.users = response.data));
}

http://localhost:3000/users/listにアクセスすると以下のようなページが表示されるはずです。

https://i.gyazo.com/6793f9c6e7c12b3800b089c16ddb28d4.png

ユーザ詳細ページ

続いてはユーザ詳細ページを表示させます。これは前回の記事と同じような内容です。

詳細ページを作成するためにファイル名は「_ + プロパティ名」でした。今回のユーザデータの場合、_id.vue_name.vueなどが作成可能です。

それでは以下のようなファイル作成してみましょう。

<!-- pages/users/_id.vue -->
<template>
<div>
  <h1>ユーザ{{ user.id }}</h1>
  <h2>{{ user.name }}</h2>
  <p>{{ user.email}}</p>
</div>
</template>

<script>
import axios from 'axios';

export default {
  head(){
    return {
      title: this.user.name
    }
  },
  data(){
    return {
      user: {},
    }
  },
  mounted(){
    axios.get('https://jsonplaceholder.typicode.com/users/' + this.$route.params.id)
          .then(response => this.user = response.data);
  }
}
</script>

mounted時にvue-routerのパラメータと一致するユーザ情報を受け取っています。

mounted(){
  axios.get('https://jsonplaceholder.typicode.com/users/' + this.$route.params.id)
        .then(response => this.user = response.data);
}

http://localhost:3000/users/1にアクセスすると以下のようなページが表示されるはずです。

https://i.gyazo.com/7387ba39dc8d28c264c0dd213ee85623.png

ユーザ一覧からユーザ詳細ページにアクセスできるようにしましょう。先ほどのユーザ一覧ページのファイルのtemplateタグ内を以下のようにします。

<template>
  <v-list three-line>
    <template v-for="user in users">
      <v-list-item
        :key="user.id"
        nuxt <= 追加
        :to="{ name: 'users-id', params: { id: user.id } }" <= 追加
        >>
        <v-list-item-content>
          <v-list-item-title v-html="user.name"></v-list-item-title>
          <v-list-item-subtitle v-html="user.email"></v-list-item-subtitle>
        </v-list-item-content>
      </v-list-item>
    </template>
  </v-list>
</template>

v-list-itemnuxtプロパティを追加することでリンクがnuxt-linkであることを指定します。 :to="{ name: 'users-id', params: { id: user.id } }"は前回の記事と同じ実装方法です。

これでユーザ一覧ページから詳細ページに遷移できます。

asyncData

現状のユーザ一覧ページと詳細ページはどちらも先にページが読み込まれ、その後axiosで取得したデータを表示する実装になっています。例えば、ユーザ一覧ページの場合、先にヘッダーやナビゲーションバーなどが読み込まれて、その後にユーザ一覧が表示されます。

https://i.gyazo.com/0013742f7e1b265f96a675e21fb91615.gif

しかし、二度表示される実装はあまりきれいではありません。ユーザ一覧も同時に表示させたいところ。

そこで使用するのがasyncDataプロパティです。

データの取得

asyncDataはNuxtのライフサイクルの一つです。レンダリングされる前に外部APIからのデータを取得することができるようです。

https://i.gyazo.com/b0caac114f7829cc822fb269c410b11b.png

Vue.jsではこのライフサイクルがないため、mounted時にAPIデータを取得するしかなく、ページ表示とずれたタイミングで取得データを表示してしまいます。しかし、asyncDataを使用すればページを表示する前にデータを取得することができるので一度の表示で完結します。

ちなみに下記のように記載するようです。見た目がdataプロパティに似ていますね。

asyncData() {
  return axios
    .httpリクエスト(url)
    .then(response => {
      return {
        データ名: response.data
      };
    });
}

asyncDataでユーザ一覧ページ

百聞は一見にしかず。まずはユーザ一覧ページを以下のように変更します。

<template>
  <v-list three-line>
    <template v-for="user in users">
      <v-list-item
        :key="user.id"
        nuxt
        :to="{ name: 'users-id', params: { id: user.id } }"
        >>
        <v-list-item-content>
          <v-list-item-title v-html="user.name"></v-list-item-title>
          <v-list-item-subtitle v-html="user.email"></v-list-item-subtitle>
        </v-list-item-content>
      </v-list-item>
    </template>
  </v-list>
</template>

<script>
import axios from "axios";

export default {
  asyncData() {
    return axios
      .get("https://jsonplaceholder.typicode.com/users/")
      .then(response => {
        return {
          users: response.data
        };
      });
  }
};
</script>

これによりデータを取得した後にページが表示されるようになりました。

https://i.gyazo.com/41160f2ac28eec699ab22d0158784123.gif

context

ユーザ一覧ページと同じように既にあったmoutedを置換する形で実装することはできません。asyncDataのライフサイクル時にthisにアクセスすることができないからです。

asyncData は pages でのみ使用可能で、このフック内では this にアクセスすることはできません。

データの取得

ではどうするのかというとコンテキストを使用します。

context は、Nuxt から Vue コンポーネントに追加のオブジェクト/パラメータを提供し、asyncData、fetch、plugins、middleware、nuxtServerInit のような特別な Nuxt ライフサイクル内で使用できます。

コンテキスト

contextの中身を確認するためにconsole.logで出力してみましょう。

<!-- pages/users/_id.vue -->
<template>
  <div></div>
</template>

<script>
import axios from "axios";

export default {
  async asyncData(context) {
    console.log(context);
  }
};
</script>

するとコンソールに膨大なデータが格納されていることがわかります。

https://i.gyazo.com/b0ab031756b266fe77cddcb93ff87642.gif

これを使用して詳細ページを表示させます。

asyncDataでユーザ詳細ページ

以下のように書き換えましょう。

<!-- pages/users/_id.vue -->
<template>
  <div>
    <h1>ユーザ{{ user.id }}</h1>
    <h2>{{ user.name }}</h2>
    <p>{{ user.email }}</p>
  </div>
</template>

<script>
import axios from "axios";

export default {
  async asyncData(context) {
    const response = await axios.get(
      "https://jsonplaceholder.typicode.com/users/" + context.params.id
    );
    return { user: response.data };
  }
};
</script>

こちらも一度にページが表示されるようになりました。

https://i.gyazo.com/73c4fcfd73b48fd4c1e4b438603c5c67.gif

【API】Speaker Recognition 概要

はじめに

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

最近は就活と仕事でそこまでプログラミング学習ができていないのですが、プログラミングを日々学習するのがエンジニアということで次に作るポートフォリオを考えています。

最近自分が通っているスクールの受講生がある面白いサービスをリリースしました。Voice Componentというサービスです。

声の成分分析ツール『 Voice component 』

これは自分の声にどんな成分が入っているのかを分析するサービスです。オリジナリティがすごい!!!

ちなみに下記がサービスのQiita記事になります。

【個人開発】「自分の声」が嫌いな人ほど使ってほしい!声成分分析サービス『Voice component』を作りました! - Qiita

使用しているAPIは大変興味深くアプリ作成者に連絡してみたところ下記APIを使用したようです。

Speaker Recognition の概要 - Speech サービス - Azure Cognitive Services

かなり面白そうですがドキュメントの量はすごい少ない印象ですね。しかし、いつか自分も使ってみたいと思えるAPIだなと思いました!ということで、少しこのAPIについて調べてみたことを今回この記事でまとめてみたいと思います!

概要

概要についてですが公式の説明をそのまま引用させていただきます。

Speaker Recognition サービスは、音声生物測定学を使用して、固有の音声特性で話者を確認および識別するアルゴリズムを提供します。 Speaker Recognition は、"だれが話しているのか" という質問に回答するために使用されます。 1 人の話者のオーディオ トレーニング データを提供すると、話者の声の固有の特性に基づいて登録プロファイルが作成されます。 次に、このプロファイルに対してオーディオ音声サンプルをクロスチェックして、話者が同じ人物であることを確認します (話者認証)。または、登録されている話者プロファイルの "グループ" に対してオーディオ音声サンプルをクロスチェックして、グループ内の任意のプロファイルと一致するかどうかを確認します (話者識別)。

一つずつ追っていきます。

peaker Recognition サービスは、音声生物測定学を使用して、固有の音声特性で話者を確認および識別するアルゴリズムを提供します。

"音声生物測定学"!?!?!?!?!?!?!?!?!?

とても難しそうな単語ですね、、、、。しかし、あとの文(固有の音声特性で話者を確認および識別するアルゴリズムを提供)から推測するに、音声の識別を利用することができるAPIだとわかります。

1 人の話者のオーディオ トレーニング データを提供すると、話者の声の固有の特性に基づいて登録プロファイルが作成されます。

"オーディオ トレーニング データ"と訳されていますが、これは音声データのことでしょう。音声データを元に音声固有の特性に基づいてデータが登録される使用ですね。例えば、自分の喋っている音声データをこのAPIに伝えることで、自分の声を分析してその特性を固有のデータとして登録してくれるわけですね!

次に、このプロファイルに対してオーディオ音声サンプルをクロスチェックして、話者が同じ人物であることを確認します (話者認証)。または、登録されている話者プロファイルの "グループ" に対してオーディオ音声サンプルをクロスチェックして、グループ内の任意のプロファイルと一致するかどうかを確認します (話者識別)。

登録した音声データを用いで2つの用途があるようですね。1つは話者認証。これは登録してある音声データと同じ特性を持っているかどうかをチェックする用途ですね。まさに認証のことです。

それに対して、話者識別は登録されているデータの "グループ" (音声の特性ごとのグループ)に当てはまるかどうかをチェックするようです。認証とは違い、その人の声の性質と登録されている音声データの特性の比較をする機能です。これが上述したVoice Componentの機能のようですね。

https://i.gyazo.com/8aca811f69cbd3dee3bf443e00872790.png

Speaker Recognition | Microsoft Azure

ここまで読んでわかることは

  1. 音声データの特性をデータとして保存
  2. 別の音声と登録されているデータが同じであるか(話者認証)、どのグループに属するか(話者識別)をチェック

APIでできる大まかな機能のようです。

次回までにやること

ここまで調べてもめちゃくちゃ興味が湧いてきました。次回からは実際にJavascriptのコードを見て使用方法を見ていきたいと思います。ただし、自分はRailsがほんの少しわかる程度ですので、別言語のコードリーディングは推測を含むことはご了承くださいmm。公式ドキュメントの解説言語が如何せん4つしかないので、、、

https://i.gyazo.com/246423394ed8b120460912de1f324df9.png

Azure-Samples/cognitive-services-speech-sdk

あとは、アプリを実際に作った人と話す機会も作りましたのでその方から色々と聞いてきたいと思います。

【Nuxt】ルーティング

はじめに

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

前回の記事ではNuxtの自動ルーティングについて少し触れましたが、今回の記事でさらに深掘りしたいと思います。

事前知識

前の記事のおさらいです。Nuxtを使う最大のメリットの一つは自動ルーティングができることです。pagesディレクトリ直下にファイルを作成することで、そのファイルへのルーティングが自動で行われます。

pagesディレクトリ直下にabout.vueを作成します。

<!-- pages/about.vue -->
<template>
  <div>
    <h1>Aboutページ</h1>
  </div>
</template>

これをするだけでhttp://localhost:3000/aboutにアクセスすることで作成したファイルのページが追加されています。

https://i.gyazo.com/5268414a26c4b130c080a10c006e88ae.png

階層ページ

フォルダを作成し、その配下にファイルを作成することで 階層ページを作成できます。

試しにusersディレクトリを作成し、その下にlist.vueを作成します。

<!-- pages/users/list.vue -->
<template>
  <h1>ユーザ一覧ページ</h1>
</template>

その後再コンパイルすると、http://localhost:3000/users/listで作成したファイルのページを表示できます。

https://i.gyazo.com/4f7bffa24258fff1c285ade187d39d03.png

コンパイルした後に.nuxt/router.jsのファイルを確認すると、ルーティングが正しく反映されているかを確認することもできます。

// .nuxt/router.js
routes: [{
    path: "/about",
    component: _4757573d,
    name: "about"
  }, {
    path: "/inspire",
    component: _6036b20a,
    name: "inspire"
  }, {
    path: "/users/list",
    component: _39e8efc5,
    name: "users-list"
  }, {
    path: "/",
    component: _670f3202,
    name: "index"
  }],

  fallback: false
}

今回作成したファイルのルーティング指定箇所は以下の部分ですね。

    path: "/users/list",
    component: _39e8efc5,
    name: "users-list"

componentプロパティは「_(アンスコ) + 8桁の英数字」で自動で生成されています。nameプロパティは「フォルダ名 + ファイル名」で自動で生成されています。

このページへのルーティングをnuxt-linkタグを使用して生成します。nameプロパティをそのまま参照すればOKです。

<!-- components/NavBar.vue -->
<template>
  <div>
    <nav>
      <nuxt-link to="/">Home</nuxt-link>
      <nuxt-link to="/about">About</nuxt-link>
      <nuxt-link :to="{ name: 'users-list' }">User List</nuxt-link>
    </nav>
  </div>
</template>

このように簡単にページへの遷移先リンクを作ることができます。

https://i.gyazo.com/0cfd05ca82ff2869abbf7b71d99532c6.gif

Dynamic Routes(動的ルート)

詳細ページなど、動的にページの内容が変わるページの作成方法もnuxtでは簡単にできます。

id.vueをusersディレクトリの下に作成しましょう。注意点としては、動的に内容が変わるページの**ファイル名の前には必ず(アンダーバー)が必要**であることです。以下を参照してください。

ファイルシステムに基づくルーティング

ファイルの中身が動的に切り替わっているかを確かめるために、this.$route.params.idでパラメータが反映されているかを確認します。

<!-- pages/users/_id.vue -->
<template>
  <h1>ユーザID: {{ this.$route.params.id }}</h1>
</template>

http://localhost:3000/users/1

https://i.gyazo.com/dbf233807e08c9616de8f06f66be1e04.png

http://localhost:3000/users/2

https://i.gyazo.com/ca3c97996a623973e312471ad5e0e033.png

動的に変更しているのがわかります。

では実際にデータを入れてみましょう。本来であればaxiosを使ってデータを取得するのですが、今回はデータをファイルの中に直接定義します。

<!-- pages/users/_id.vue -->
<template>
  <div>
    <h1>ユーザID: {{ user.name }}</h1>
    <ul>
      <li v-for="user in users" :key="user.id">
        <nuxt-link :to="{ name: 'users-id', params: { id: user.id } }">{{
          user.name
        }}</nuxt-link>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      users: [
        {
          id: 1,
          name: "Aさん"
        },
        {
          id: 2,
          name: "Bさん"
        },
        {
          id: 3,
          name: "Cさん"
        }
      ]
    };
  },
  computed: {
    user() {
      return this.users.find(user => user.id == this.id);
    },
    id() {
      return this.$route.params.id;
    }
  }
};
</script>

nuxt-linkタグを繰り返し処理で生成しています。nuxt-linkタグに動的ルーティングを反映させるためには、nameプロパティの指定に加えて、パラメーターも指定してあげます。

<ul>
  <li v-for="user in users" :key="user.id">
    <nuxt-link :to="{ name: 'users-id', params: { id: user.id } }">{{
      user.name
    }}</nuxt-link>
  </li>
</ul>

これで詳細ページのルーティングができていることが確認できると思います。

https://i.gyazo.com/68094e6999f5ce2a7a8672c683db3c0d.gif

prefetch

Nuxt Components

ビューポート内やスクロール時にリンクが表示されていることを検出し、ユーザーがリンクをクリックしたときにすぐに使えるように、それらのページのデータを事前に取得できる機能です。nuxt-linkタグにはデフォルトで使えるようになっています。

あまり使いどころがイメージできていないのですが、遷移する前に何かしらの処理を挟みたいときなどに使えそうですね。

prefetchを停止することもできます。(ただ、停止するメリットはあまりわかりませんでした。)

タグごとに個別に停止

<NuxtLink to="/about" no-prefetch>About page not prefetched</NuxtLink>
<NuxtLink to="/about" :prefetch="false">About page not prefetched</NuxtLink>

グローバルに停止

// nuxt.config.js  
router: {
    prefetchLinks: false
  }

【Nuxt】プロジェクト立ち上げ

はじめに

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

10日間ほど更新できていませんでしたので久しぶりの投稿となります。

今月はVue.jsのフレームワークであるNuxt.jsを中心に投稿していきたいと考えています。

Nuxt.js - ユニバーサル Vue.js アプリケーション

最初ということで簡単にプロジェクトを作成する方法から入ります。

プロジェクト作成

yarnで下記コマンドを実行します。

$ yarn create nuxt-app <project-name>(任意の名前)

その後複数の質問が聞かれます。自分の好きな設定を書きましょう。

  • プロジェクトの名前
? Project name: sample
  • JSかTSか(TSはわからないのでJSにしました)
? Programming language: (Use arrow keys)
❯ JavaScript 
  TypeScript
  • YarnかNpmか(Yarnにしました)
? Package manager: (Use arrow keys)
❯ Yarn 
  Npm

すごくたくさんのフレームワークに対応していて驚きました!ここは自分の好きなVuetify.jsを選択。

? UI framework: (Use arrow keys)
❯ None 
  Ant Design Vue 
  BalmUI 
  Bootstrap Vue 
  Buefy 
  Chakra UI 
  Element 
  Framevuerk 
  Oruga 
  Tachyons 
  Tailwind CSS 
  Windi CSS 
  Vant 
  View UI 
  Vuetify.js
  • Nuxtのモジュール選択(Axiosを選択)
? Nuxt.js modules: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◯ Axios - Promise based HTTP client
 ◯ Progressive Web App (PWA)
 ◯ Content - Git-based headless CMS

このモジュールについては補足します。

  • Axios - Promise based HTTP client

直訳すると、Promise ベースの HTTP クライアント。

PromiseというのはJSの非同期処理の時に使うものですよね。HTTP clientはリクエストを送ってレスポンスを受け取るやつ。つまり、非同期でリクエストを送ってレスポンスを受け取る。そのまんまでした。

[axios] axios の導入と簡単な使い方 - Qiita

  • Progressive Web App (PWA)

WebサイトのコンテンツをアプリとしてスマートフォンやPCにインストール可能にする技術

PWA(Progressive Web Apps)はどうスゴイのか?基本知識と12のメリットを解説

Apple Storeだったり、PCだったりにアプリとしてインストールするようにします。Nuxtのモジュールであるんですね。すごい。

  • Content - Git-based headless CMS

GitベースのヘッドレスCMS

nuxt/content

CMSとはなんでしょう。

CMSを導入していないWebサイトでは、「1ページずつ完成形のWebページのデータを作成し、それを保存・管理している」 CMSを導入しているWebサイトでは、「データベース上に個別に保存されている画像やテキスト、テンプレートなどのデータを、CMSが必要に応じて取り出して、Webページを自動的に生成している」

CMSとは?初心者でもわかるCMSの基礎知識とメリット、導入事例|デジタルマーケティングソリューション|日立ソリューションズ

GitベースということでGitに保存してあるファイルを必要に応じて取り出しWebページを生成しているというイメージでしょうか?

これ以上は本筋からずれるので、後日ブログに投稿したいと思います!

  • Linterの選択(EsLintを選択)
? Linting tools: 
❯◯ ESLint
 ◯ Prettier
 ◯ Lint staged files
 ◯ StyleLint
 ◯ Commitlint
  • テストを選択(Noneを選択)
? Testing framework: (Use arrow keys)
❯ None 
  Jest 
  AVA 
  WebdriverIO 
  Nightwatch
  • ファイルのレンダー方法(Single Page Appを選択)
? Rendering mode: (Use arrow keys)
❯ Universal (SSR / SSG) 
  Single Page App

下の記事を参照すれば理解できると思います。

Nuxt.jsにおけるSPA,SSR,SSGの使い方と挙動 - Qiita

  • デプロイ先(Serverを選択)
? Deployment target: (Use arrow keys)
❯ Server (Node.js hosting) 
  Static (Static/Jamstack hosting)
  • 開発ツール(vscodeなのでjsconfig.jsonを選択)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◯ jsconfig.json (Recommended for VS Code if you're not using typescript)
 ◯ Semantic Pull Requests
 ◯ Dependabot (For auto-updating dependencies, GitHub only)
? Continuous integration: (Use arrow keys)
❯ None 
  GitHub Actions (GitHub only) 
  Travis CI 
  CircleCI
  • バージョン管理ツール(Gitを選択)
? Version control system: (Use arrow keys)
❯ Git 
  None

これでプロジェクトが作成されました!

$ yarn dev
yarn run v1.22.10
$ nuxt

   ╭───────────────────────────────────────╮
   │                                       │
   │   Nuxt @ v2.15.7                      │
   │                                       │
   │   ▸ Environment: development          │
   │   ▸ Rendering:   client-side          │
   │   ▸ Target:      server               │
   │                                       │
   │   Listening: http://localhost:3000/   │
   │                                       │
   ╰───────────────────────────────────────╯

これをすればhttp://localhost:3000にアクセスすると以下の画面が表示されます。

https://i.gyazo.com/5d6ea8da61b1be5e53404f8341fec8e9.png

デフォルトの画面からすでにかっこいいです!

自動ルーティング

これだけ見るとVueを立ち上げた時の設定と少し違う方法でプロジェクトを立ち上げただけとなりますが、そうではありません。nuxt.jsでは自動ルーティングというとても便利な機能があります。

見るはいうより易し。まずは実装してみます。

以下のファイルを作成してください。

<!-- pages/about/index.vue -->
<template>
  <div>about</div>
</template>

その後、http://localhost:3000/aboutにアクセスします。以下のように表示されているはずです。

https://i.gyazo.com/2b05fa02c97d2c63ac3d33e56c25e03a.png

そうなんです。pagesディレクトリは以下にフォルダを作成するだけでnuxt.jsがよしなにvue-routerと紐づけてくれます。ルーティングを勝手にやってくれるなんて本当にありがたいです!

どこでページを表示しているのかというとdefault.vueにあるNuxtタグです。

<template>
    <v-main>
      <v-container>
        <Nuxt /> <=これ!
      </v-container>
    </v-main>

このNuxtタグがpagesの中のファイルを自動で読み込んでくれています。感謝✨

参考

Nuxt.js - ユニバーサル Vue.js アプリケーション

【完全ガイド】ゼロからしっかり理解したい人向けのNuxt.js入門 | アールエフェクト