6時だョ!!全員集合!!

Rails・JavaScrictを中心にアウトプットします。

2022年8月21日 DBのパフォーマンス考慮などのメモ (byやの)

内容と経緯

実務にてパフォーマンスに関する相談があり、その中で話題になった知識や相談に乗る上(パフォーマンスを考慮する上)で意識すると良い点などを走り書きのメモにはなりますが、こちらに書き起こしておきます。

パフォーマンス

  • 遅延処理(ジョブキュー)
    • レコードを大量に発行するような重たい処理はsidekiqなどderayed jobなどのライブラリを使用するのが一般的
      • ジョブ(処理の内容)、キュー(ジョブを管理する場所)、ワーカー(キューを監視し、キューにジョブが保存されたら、キューからジョブを取り出し、そのジョブを実行)
      • ジョブ、キュー、ワーカーは昔は自前で実装していたが現在ではsidekiqなどのライブラリが出てきたためよしなに管理が出来る。
  • DB
    • indexを貼る
      • keyをメモリに設定することで高速検索ができる
      • 数千〜数万はインデックスを貼っておけばパフォーマンスは大丈夫(数秒)
      • 数百万レコードなどになると色々な条件によるが数十秒かかったりする(UX的には致命的)
      • ローカルではレコードが少ないので早いのは当たり前
      • サーバーを増やす・不要なレコードを削除するなどの対応がある(基本)
        • 基本的には正解はなく出来ることを全部やることでパフォーマンスを強化する
        • ヒアリングが重要
          • 不要なデータがあるか
          • 最適解はヒヤリングして組み合わせてオリジナルで提案する
          • ログ化が適切かなども視野に入れてみる
    • ログ化
      • データベース上で管理しないでよくなったデータはログにおくことでレコードを削ることでパフォーマンスを改善する
      • テキストやCSVに書き出してダウンロード出来るようにするなど
      • 他にもサマリーなどは事前に集計しておいてデータを出すなどアイデアもある
    • Amazon RDS
      • Amazon Aurora (クラウドのために再設計されたデータベース)にするとパフォーマンスが上がる
        • 冗長構成する(横展開)
        • 値段が高い
    • SQL
      • 並び替え処理などをデータベースで行うとレコードをすべて並び替えするなどしてしまうので重くなる
      • データベース内のレコード全体に影響を与えてしまうことがあるため、取得したあとにcontrollerで処理を加えるなどを考慮する書き方が出来るようになると良い

Web関連技術

  • クローリング&スクレイピング
    • Selenium や Mechanizeなどのライブラリを使われることが多い
    • スクレイピングはサイト内の情報を取得する技術
    • クローリングはプログラムが「Webサイトを定期的に巡回し、情報を取得・保存する技術

2022年7月10日 instanceofよりも安全にインスタンスかどうかの確認ができる、プライベートフィールドのin演算子

instanceof と inの違い

instanceof
object instanceof constructor

instanceof 演算子は、object のプロトタイプチェーンに constructor.prototype が存在することを検査します。

in
prop in object

in 演算子は、指定されたプロパティが指定されたオブジェクト(またはプロトタイプチェーン)にある場合に true を返します。

in演算子のpropの部分には、プロパティ名を書きます。

プロパティ名または配列のインデックスを表す文字列式またはシンボルです(シンボルではない場合は、文字列に強制変換されます)。

propは基本的に文字列で指定します。

今回学んだprivate宣言できる#のような特殊文字を使用した場合の挙動に着目して検証してみました

class MyClass {
  #brand
  static isMyClass(object) {
    return '#brand' in object;
  }
}

console.log(MyClass.isMyClass(new MyClass())); // false
  • 文字列リテラルを使用した上で#brandとすると、#までも文字列として認識されるので結果はfalseを返しています。 これではin演算子を使用しての求める結果と異なります。

#brandはMyClassのプロパティに存在しているのでtrueなのですが、特殊文字である#特殊文字として扱いたいため、文字列リテラルで囲ってはいけないのです。 では、publicとしての挙動をみてみます。

class MyClass {
  brand
  static isMyClass(object) {
    return 'brand' in object;
  }
}

console.log(MyClass.isMyClass(new MyClass())); //true
  • brandを文字列リテラルで囲わなければ、brandは未定義のエラーになります。
  • 基本的にはin演算子のpropはこのように文字列を指定しないといけません。
  • 特殊文字を指定するときに注意が必要で基本は文字列リテラルを使用します。

instanceofとinの違いのまとめ

instanceofは、オブジェクトのプロトタイプチェーンにconstructor.prototypeが存在するかを判定します。inはプロパティがオブジェクト(またはプロトタイプチェーン)に存在するかを判定します。

参考

in 演算子

instanceof

2022年7月2日 JavaScriptの最新仕様ES2022を深ぼる

1. クラスフィールド宣言ができるようになった

そもそもクラスフィールドとは

フィールドはクラスの中でデータの値を保管するために使用するものです。

メリット

  • 今までのClass構文ではconstrutor()でフィールドを持たせる必要がありましたが、ES2022からはクラスに直接フィールドを書くことで、インスタンスにフィールドを持たせられるようになりました。
従来のクラスフィールド宣言の場合
class Human {
  constructor() {
    this.name = 'yano';
  }
}

const human = new Human();
console.log(human.name); // yano
ES2022のクラスフィールド宣言の場合
class Human {
  name = 'yano';
}

const human = new Human();
console.log(human.name); // yano

実際にブラウザのコンソールで実行すると、期待通りの出力結果が得られました。

従来の書き方とES2022の書き方は共存できるのか気になったので検証してみました

class Human {
  constructor() {
    this.name = 'yano';
  }
  name = 'yukiHaga'
}

const human = new Human();
console.log(human.name); // 'yano'
  • この場合は共存することはできずconstructorの中の定義が優先されます。

スタティックなクラスフィールド宣言

class Human {
  static category = "human"
}

console.log(Human.category);  // human
  • 従来ならばnew演算子を使用してインスタンス化しないとアクセスできなかったのに対してHuman.~ という形でアクセスできています。

こちらも従来の書き方とES2022の書き方は共存できるのか気になったので検証してみました

class Human {
  constructor() {
    this.name = 'yano';
  }
  static name = 'yukiHaga'
}
console.log(Human.name); // 'yukihaga'
const human = new Human();
console.log(human.name); // 'yano'
  • staticなクラスフィールド宣言はconstructorは共存できます。

2. プライベートなフィールドとメソッドが使えるようになった

メリッド

フィールドとメソッドをプライベートにすることで、クラス外からの値の上書きを防ぐことができます。(普通にフィールドとメソッドを定義すると、パブリックとして定義されます。)

#を使ってプライベートなフィールドを宣言する

ES2022でのprivateの宣言は# を使用します。

class MyClass {
  // プライベートなフィールド
  #name;
  
  constructor(name) {
    this.#name = name
  }

  hello() {
    console.log(`こんにちは${this.#name}さん!`)
  }
}

const foo = new MyClass("yano");
foo.hello(); // 「こんにちはyanoさん!」と出力される
  • 従来は _をつける事でprivateな書き換えをしないメソッドだと慣習的に決まっていただけで、ただのルールであり仕様ではありませんでした。
  • 今回の# を使うことによって、書き換えをしようとするとエラーになるようになり、きちんとした仕様になりました。
エラーを出すコード例
class MyClass {
  // プライベートなフィールド
  #name;
  
  constructor(name) {
    this.#name = name
  }

  #hello() {
    console.log(`こんにちは${this.#name}さん!`)
  }
}

const foo = new MyClass("田中");
// 以下の行でエラーが発生する
foo.#hello = () => console.log('アイウエオ')

foo.#hello()

エラー内容

syntaxError: Private field '#hello' must be declared in an enclosing class

(プライベートフィールド '#hello' は、包含するクラスで宣言する必要があります。)
  • #の付いているhelloメソッドに対して、console.log('アイウエオ') に書き換えを行なっているのでエラーとなっています。

まとめ

  • 従来のJSは慣習でprivateを_をつけることで定義していただけで仕様ではなかったため、全てがpublicでした。
  • ES2022からprivateを# で定義して、書き換えを行なった際にエラーが出力されるようになった事で、言語の仕様としてprivateを持たせられるようになりました。

クラスフィールドとprivateメソッドは組み合わせられる

class MyClass {
  // public field
  foo = "public field: 大部屋";
  // private field
  #bar = "private field: 個室";

  // public static field
  static cafeteria = "public static field: 食堂";
  // private static field
  static #japaneseStyleRestaurant = "private static field: 料亭";

  // public method
  orange() {
    return console.log("public method: みかん");
  }

  // private method
  #grape() {
    return console.log("private method: ぶどう");
  }

  // public static method
  static staticOrange() {
    return console.log("public static method: みかん");
  }

  // private static method
  static #staticGrape() {
    return console.log("private static method: ぶどう");
  }
}
  • 冒頭で学んだクラスフィールドとpublic privateそれぞれの定義です。
  • publicのものは書き換え可能ですが、privateのものは書き換えが不可です。

TypeScriptのprivateとES2022の# の違い

トランスパイル後の状態が違います。

  • TypeScriptのprivateはVSCode上でのコンパイルエラーとしてエラーを出力してくれるので実装上ではprivateが機能しているが、トランスパイル後にJSのコードに変換されたときには、publicなものになっている。
  • ES2022の#ではトランスパイル後も#となっており、privateなものとして読み込まれる。

これは、どちらでも問題がないため、チームの方針に従うべきと考えます。

参考

https://zenn.dev/tonkotsuboy_com/articles/es2022-whats-new

2022年5月29日 アルゴリズム問題をTypeScriptやRubyで解く (by やの)

問題

https://algo-method.com/tasks/213

◇ 問題の意図

配列の中から一番大きな数字を取り出してほしいようです。


Rubyで解いてみる

◇ 解く過程
◇ 提出したコード
n = gets.to_i
a = gets.split.map(&:to_i)

puts a.max


TypeScriptで解いてみる

◇ 解く過程
  • 一瞬命名に悩みこちらを参考にしました。
    • 『関数 命名 get fetch』検索でヒットした記事
  • 問題を解くための関数を検索
    • JavaScript 配列 大きい要素』検索でヒットした記事 - MDN
    • Math.max()の引数には配列を渡すとNaNが返されるため、スプレッド構文で展開する必要がありそうです。
◇ 提出したコード
import * as fs from 'fs'
const input       = fs.readFileSync("/dev/stdin", "utf8")
const [nv, alist] = input.split("\n")

const [n, v]      = nv.split(" ").map(Number)
const a           = alist.split(" ").map(Number)

type SelectMaxNumber = {
    (array: number[]): number;
}

const selectMaxNumber: SelectMaxNumber = (array) => {
    return Math.max(...array);
}

console.log(selectMaxNumber(a));


コメント

Rubyは超シンプルになりましたねー。笑
TypeScriptは書いていて楽しいです!

頭の体操になりました〜! 今日も一日頑張りま〜〜す♪

2022年5月21日 番外編アルゴ式

今回の問題

  • 配列の要素と右隣の数字を比較して右隣が大きいケースの回数をカウントするという内容です。

解答

yano's answer

  • Array.prototype.reduce()を使用しました。
  • countを出す・配列のindexを使って操作する点などでreduceが妥当と判断しました。
//(入力受け取り 省略)
type CountUpNumberInArray = {
  (numArray: number[]): number
}

const countUpNumberInArray: CountUpNumberInArray = (numArray) => 
  numArray.reduce((accumulator, currentValue, index, array) => {
    return array[index] < array[index + 1] ? accumulator += 1 : accumulator += 0
  }, 0);

console.log(countUpNumberInArray(a));
  • 関数式の後にreturnを書いた方がわかりやすいかもしれません。
  • reduceの第二引数は使用しないため、アンダースコアで省略した方がいいです。

修正

const countUpNumberInArray: CountUpNumberInArray = (numArray) => {
  return numArray.reduce((accumulator, _, index, array) => {
    return array[index] < array[index + 1] ? accumulator += 1 : accumulator += 0
  }, 0);
}
  • 命名ももう少し拘れると良さそうです。
Rubyでやってみる
  • for in文パターン
n = gets.to_i
a = gets.split.map(&:to_i)

count = 0

for i in 0..n do
  count += 1 if a[i].to_i < a[i + 1].to_i
end

puts count
n = gets.to_i
a = gets.split.map(&:to_i)

count = 0

a.each_with_index do |value, index|
  count += 1 if a[index] < a[index +1].to_i
  if index == a.size - 1
    puts count
  end
end

yuki's answer

  • 素早く命名するのが苦手だと感じました。今後、改善していきます。(現時点の命名も本当に分かりやすいか疑問)
  • reduceを使えば、配列の2つの要素を比べられるので、reduceを使えば良かったです。関数型プログラミングをなるべく自分のコードに取り入れたいと思いました。
import * as fs from 'fs'
const input       = fs.readFileSync("/dev/stdin", "utf8")
const [nv, alist] = input.split("\n")

const [n, ..._]  = nv.split(" ").map(Number)
const list        = alist.split(" ").map(Number)

const countRightThanLeft = (list: number[]): number => {
  let count = 0;
  for (let i = 1; i < list.length; i++) {
    if (list[i-1] < list[i]) {
      count++;
    }
  }
  return count;
}

console.log(countRightThanLeft(list));

yui's answer

import * as fs from 'fs'
const input       = fs.readFileSync("/dev/stdin", "utf8")
const [nv, alist] = input.split("\n")

const [n,v,..._]  = nv.split(" ").map(Number)
const list        = alist.split(" ").map(Number)

type ArrayType = {
    (previousValue: number, currentValue: number, currentIndex: number, array: number[]): number;
}

const reducer: ArrayType = (previousValue, _, currentIndex, array) => {
    const count =  (array[currentIndex] < array[currentIndex + 1]) ? 1 : 0;
    const returnNumber = previousValue + count;
    return returnNumber;
}

console.log(list.reduce(reducer, 0));
  • 今回は、2つの要素の値を比較したいのでreduce()を使いました。
  • reduceメソッドは、配列のそれぞれの要素に対して、順番通りに、コールバック関数を呼び出します。コールバック関数の引数には、累積値, 要素, インデックス, 配列を渡すことが出来ます。配列のすべての要素に対してコールバック関数を実行した結果が戻り値となります。

  • 最初、変数にアノテーションを記述していましたが、型推論を利用するように変更しました。

    • プリミティブ型の値を使うときは、アノテーションを書かずに型推論を使う方が良いです。なぜなら、間違えて型定義してしまうことを防ぐことができるからです。
    • 対して、オブジェクトや関数の引数などはアノテーションを書く方がいいです。オブジェクトを変数に入れた時、プロパティにどんな値が入っているかに関わらず単なるobject型型推論されてしまうからです。
  • 関数の引数として、アンダーバー_が使われている記述があります。これは、使われない引数であることを明示しています。使われない値であるが、単純に引数として書く必要があるため、アンダーバーを使用しています。

アンダーバーのみの変数の意味

参考

アルゴ式

2022年5月20日 投稿機能を作成

今回実装した機能

  • 投稿一覧ページにログインユーザーの新規投稿ページへ遷移するボタンを実装 Image from Gyazo

仕様

  • userとpostを紐付ける

  • new Postリンクをクリックすると、投稿フォームが出る

  • 投稿に成功したら、投稿一覧に遷移する

  • 投稿に成功したら、「投稿に成功しました」というフラッシュメッセージを出す

実装を通した気づき

Yano
  • 外部キーの途中追加・・・rails g migration add_user_id_to_posts
    • migrationファイルに追記
class AddUserIdToPosts < ActiveRecord::Migration[6.0]
  def change
    add_reference :posts, :user, foreign_key: true
  end
end
  • ログインしているユーザーの持つpostを新規作成
    • saveに成功した場合はユーザーの投稿一覧ページに遷移させる
    • user_posts_path(current_user)とする。
    • save!とすると投稿に失敗した場合に例外を返す。falseの処理がある場合はsaveを使用する。
  def create
    @post = current_user.posts.build(post_params)
    if @post.save
      redirect_to user_posts_path(current_user)
    else
      render 'new'
    end
  end
Yui
  • current_userは、sorceryのメソッドで現在のユーザーを取得できます。
  • ルーティングの設計方法が完全に理解できていないことに気づきました。
    • 今回投稿はユーザーモデルに紐づくためネストして記述しました。しかし、それだけだと投稿一覧ページに遷移出来ずエラーが発生しました。
    • そこで、ネストしないユーザーに紐づかないルーティングを追加するとうまく遷移できるようになりました。
Rails.application.routes.draw do
  resources :users do
    resources :posts
  end
  resources :posts
end
Yuki
  • 良いURIの設計で重要な原則は、「覚えやすく、どんな機能を持つURIなのかがひと目でわかる」ことです。
# POSTリクエストで以下のURLにアクセスした場合、
# アクセスしたユーザーのポストを作成する
/users/user_id/posts
  • ログインした時に、投稿一覧ページになっていた方が良いので、redirect_toのパスを投稿一覧ページにしました。
redirect_back_or_to user_posts_path(@user), notice: 'Login successful'

参考

Simple Password Authentication-Sorcery

RESTful APIのURI設計(エンドポイント設計)

2022年5月19日 ログイン機能 ・サインアップ機能を作成

テーブル構成

◆ usersテーブル

name:string email: string crypted_password: string salt: string

◆ postsテーブル

title:string body: text user_id:bigint(references)

◆ commentsテーブル

body: text text: string

実装

ログイン機能・サインアップ機能をsorceryで実装しました。
Simple Password Authentication

実装を通した気づき

Yano
  • debuggerで値の中身を確認することが出来ます。
  • "@user"やparamsと叩くなどして、どういったパラメータを受け取っているかなどでミスを発見しました。
[2, 11] in /Users/yanokouhei/workspace/rails-free/free/app/controllers/user_sessions_controller.rb
    2:   skip_before_action :require_login, only: [:new, :create]
    3: 
    4:   def create
    5:     @user = login(params[:email], params[:password])
    6:     debugger
=>  7:     if @user
    8:       redirect_back_or_to(:users, notice: 'Login successful')
    9:     else
   10:       flash.now[:alert] = 'Login failed'
   11:       render action: 'new'
(byebug) @user
#<User id: 3, email: "yano2@yano", crypted_password: "$2a$10$4TIvRfrA3S2NlbfGzkrs/.zr2hjnzjh2UYKfGMl3VFe...", salt: "_PMqGaxonoZgeF8M5k1z", created_at: "2022-05-19 00:33:14", updated_at: "2022-05-19 00:33:14">

コロンの位置などは注意した方が良いです。

  has_many :posts, dependent: :destroy
Yui
Yuki
  • scaffoldで生成したファイルに処理内容が自動的に書かれているのがすごいと感じました。
  • strongパラメータにちゃんと値が渡っているか気をつけるべきです。
  def user_params
    params.require(:user).permit(:email, :password, :password_confirmation)
  end

明日やること

ユーザーに紐付けた投稿機能やります!

参考

Simple Password Authentication-Sorcery