行動すれば次の現実

テック中心の個人ブログ

CSV.generateでUTF-8からShift−JISに変換したときの文字コード変換エラーを回避する

CSV.generateメソッドでUTF-8の文字列をShift-JISに変換して書き込もうとしたら

U+2613 from UTF-8 to Windows-31J 
incompatible character encodings: UTF-8 and Windows-31J

というエラーが発生しました。

docs.ruby-lang.org

上記リファレンスに記載の通り、そのまま使うと文字コード変換で失敗する可能性があるので、適宜変換などして使う必要があります。

主に機種依存文字などが当てはまりますが、一つ一つ変換処理を定義するのもイタチごっこなので、潔く変換失敗文字列は全無視する方針にしました。

実装

def sjis_safe(str)
  str.encode(Encoding::SJIS, invalid: :replace, undef: :replace)
end
pry(main)> '☓ほげほげ'.encode(Encoding::SJIS, invalid: :replace, undef: :replace).encode(Encoding::UTF_8)
=> "?ほげほげ"

使用例

CSV.generateでAppendしていく直前にsjis_safeで変換するイメージです。

csv_string = CSV.generate(encoding: Encoding::SJIS, force_quotes: true) do |csv|
  csv << [
    sjis_safe(name).encode(Encoding::UTF_8)
  ]
end

Shift-JISを扱うときはこういう考慮が必然なので早く一掃されてほしいと願います。

クエリ文字列から特定のパラメータを削除したい | Ruby On Rails

クエリ文字列から特定のパラメータのみを削除する方法を説明します。

実装

uri = URI.parse('https://www.example.com?hoge=1&fuga=2&piyo=3')
query = Rack::Utils.parse_nested_query(uri.query)
query.delete('fuga')
uri.query = query.to_param
uri.to_s

説明

(1) URL文字列をURIオブジェクトに変換します。

uri = URI.parse('https://www.example.com?hoge=1&fuga=2&piyo=3')
=> #<URI::HTTPS https://www.example.com?hoge=1&fuga=2&piyo=3>

(2) Rack::Utilsを使用してクエリ文字列をHashにします。

query = Rack::Utils.parse_nested_query(uri.query)
=> {"hoge"=>"1", "fuga"=>"2", "piyo"=>"3"}

(3) deleteメソッドで特定のパラメータを削除します。

query.delete('fuga')
=> "2"

(4) URIオブジェクトに新しいクエリパラメータをセットします。

uri.query = query.to_param
=> "hoge=1&piyo=3"

(5) to_sでURLを取得します。

uri.to_s
=> "https://www.example.com?hoge=1&piyo=3"

Railsのカスタムバリデーションで複数項目の相関をチェックしたい

開始日と終了日を入力するシステムで、期間の大小チェックと期間の日数チェックを行いたい場合があります。

カスタムメソッドで実装するケースはよく見受けられますが、カスタムバリデーションとして使い回したい思ったので実装してみました。

※あくまでも簡易実装なので国際化などは非対応です。

日付期間チェックのカスマムバリデータ

class DatePeriodValidator < ActiveModel::Validator
  def validate(record)
    start_date, end_date, max_days_period_val = options.values_at(:start_date, :end_date, :max_days_period)
    start_date_val = record.send(start_date)
    end_date_val = record.send(end_date)
    return if start_date_val.blank? || end_date_val.blank?

    if max_days_period_val.present? && (start_date_val - end_date_val).to_i.abs > max_days_period_val
      return record.errors[options[:start_date]] << "#{record.class.human_attribute_name(end_date)}の期間は#{max_days_period_val}日以内を入力してください"
    end

    return if start_date_val <= end_date_val

    record.errors[options[:start_date]] << "#{record.class.human_attribute_name(end_date)}よりも前に設定してください"
  end
end

使い方

対象のモデルに以下のような宣言を追加します。

validates_with DatePeriodValidator, start_date: :sales_start_date, end_date: :sales_end_date, max_days_period: 31
  • start_date:(必須) 開始日にあたる対象のカラム名を指定してください
  • end_date:(必須) 終了日にあたる対象のカラム名を指定してください
  • max_days_period:(任意) 有効な期間の日数を指定してください。未指定の場合は期間日数チェックは行いません。

私が行っている「1週間の振り返りと計画」の具体的な方法

振り返りの時間とはなにか?

私は毎週土曜日の午前中に1時間程度の振り返りの時間を設けています。 今週1週間の学びと反省、来週1週間の計画を行い、自分としっかり向き合うのが目的です。

忙しい日々を送っていると、つい目の前のことで手一杯になり視野が狭くなってしまいます。 そのため、強制的に振り返りの時間を設けることで人生を俯瞰することができ、目まぐるしく発生する日々の物事を根拠を持ってコントロールできると考えていいます。

本記事では私が行っている「1週間の振り返り」の具体的な方法を紹介したいと思います。

振り返りはどこでやるのか?

基本的には「自宅以外」で行うと良いと考えています。私の場合は最寄りのカフェです。 なぜ自宅以外なのかというと、「集中力が向上されるという点」と「非日常感での思考の転換できる点」というのが大きな理由です。

それぞれメリットとデメリットをあげたいと思います。

カフェで振り返りをするメリット・デメリット

〇メリット
  • 非日常の環境に身を置ける
  • 余分なものを持ち込まないため集中できる
〇デメリット
  • カフェ代が掛かる
  • カフェに行くまでの往復時間が掛かる
  • 気楽に離席できない

自宅で振り返りをするメリット・デメリット

〇メリット
  • その気になったら、いつでも振り返りができる
  • 備品を持ち歩かなくても、家にあるものは全て使用できる
  • 費用が掛からない
〇デメリット
  • いつもと同じ環境なので非日常感がない
  • いろいろなものが目に入るため気が散る
  • 家族のスケジュールに影響され、好きな日程が選べない

人によっては人混みや雑音が苦手という方もいると思うので、自分にあった場所で行うと良いでしょう。

振り返りに持っていくもの

振り返りを行う際は、以下のアイテムを持参します。

  • ペン、ノート
  • タブレット
  • スマホ

ペン、ノート

頭の整理をしながら振り返りをします。 ゼロ秒思考でメモを取ったり、図解しながら頭の整理をする際にペンとノートを使用します。

タブレット

振り返りのメインはタブレットを使用します。 後述するtodoistやmindmeisterアプリを使って振り返りを行います。

スマホ

スマホは基本的にはオフラインモードにします。l だったら持っていかなくても良いのでは?と思いますが、災害時などの緊急用として仕方なく持参しています。

振り返りの手順

ここからが本題です。振り返りは大きく3ステップで行います。

  1. 今週やったことを振り返る
  2. 今気になってることを書き出す
  3. 来週やることを計画する

1.今週やったことを振り返る

まずは今週やったことを思い出す作業です。 各媒体に振り返るための材料が残っているため、それらをもとに振り返りを実行します。

todoistを振り返る

後述しますが、タスクはtodoistというアプリで管理しています。

todoistの「完了したタスク」で過去1週間に自分が何を行ったのかを確認し、その時の状況などを頭の中に思い出します。

カレンダーを振り返る

今週のカレンダーに記載されているイベントを元に振り返ります。私はGoogleカレンダーを使用しています。

メールを振り返る

今週受信したメールをざっと振り返ります。 未読メールもここでチェックして、必要なければアーカイブまたはゴミ箱行きにします。そうすることで頭の中がスッキリします。

X(旧Twitter)のいいねを振り返る

X(旧Twitter)では自分のツイートと、いいねしたツイートを見返すことで、どんなツイートをしたのか、その時の感情などを振り返ります。

Youtubeのいいねを振り返る

自分がどんな動画にいいねしたのかを見返すことで、その時の感情などを振り返ります。

2.今気になってることを書き出す

上記の振り返り材料を元に、今気になっていることを改めてノートに書き出します。

気になっているを書き出す

今気になっていることをノートに箇条書きで列挙します。

気になっていることをさらに深堀りたい場合は、ゼロ秒思考で深堀りをしていきます。

気になっていることがある程度明確になった時点でtodoistのinboxに追加していきます。

ゼロ秒思考については下記参照

3.来週やることを計画する

いつかやるリストを確認する
  • いつかやるリストの内、来週やりたいものがあれば期限を設定してtodoistの適当なプロジェクトに移動させます。
  • いつかやるリストの内、やる必要がないと思ったものは削除します。
mindmeisterで価値観マップを確認する
  • mindmeister上の価値観マップを確認します。
  • 価値観マップを確認して来週やりたいことがあればtodoistの適当なプロジェクトに移動させます。
  • 価値観マップとは人生で大切にしたいモノ・コトを以下の観点ごとにまとめたマインドマップです
    • 精神生活
    • 健康
    • プライベート
    • 人間関係
    • 仕事
    • 会社
    • 資産
  • 価値観マップの作成方法はワン・シングという書籍に詳しく書いてあるので参考にしてみると良いかもしれません
    • 「11 成功の習慣」を参照
来週のカレンダーを確認する
  • 来週のカレンダーを確認してtodoistの適当なプロジェクトに移動させます。
inboxを確認する

todoistのinboxに追加されたタスクを確認します。

  • 2分以内でできるなら今すぐやります。
  • 来週やりたいなら期限を設定してtodoistの適切なプロジェクト(※)に移動させます。
  • 誰かがボールを持っていてpendingしているなら「連絡待ち」のプロジェクトに移動させます。
  • やる必要がないならタスクを削除します。
  • いつかやりたいならtodoistの「いつかやる」のプロジェクトに移動させます。

※ 適切なプロジェクトとは以下のいずれかである

  • 精神生活
  • 健康
  • プライベート
  • 人間関係
  • 仕事
  • 会社
  • 資産

まとめ

以上が振り返りの全容です。

1.今週やったことを振り返る 2.今気になってることを書き出す 3.来週やることを計画する

の3ステップを1週間おきに回していくという内容になっています。

この振り返りのフレームワークは、主に以下の書籍や手法を元に独自で編み出したものです。 参考としてリンクをまとめておくので、気になる方は読んでおくとフレームワークの本質が理解できるかと思います。

参考書籍
  • はじめてのGTD ストレスフリーの整理術
  • 新版 ザ・マインドマップ(R)
  • ゼロ秒思考 頭がよくなる世界一シンプルなトレーニング
  • ワン・シング 一点集中がもたらす驚きの効果
使用ツール
  • evernote
  • todoist
  • mindmeister
  • ノート
  • ペン

blueimp-load-imageを使った画像リサイズ処理の実装例 | Javascript | npm

昨今では画像アップロード処理をする際、サーバーサイドだけでなくフロントサイドでもリサイズ(圧縮)するのが主流になっています。

画像圧縮は、Javascriptのcanvas APIを使って実装するのが一般的ですが、canvasで画像圧縮処理を一から実装となると、それなりのコーディング量が必要となります。 そのため、よっぽどな理由がない限りはJavascriptライブラリを使用するのをオススメします。

今回は、代表的な画像圧縮ライブラリ「blueimp-load-image」を使用した実装例を紹介したいと思います。

blueimp-load-imageを使用した画像圧縮の実装例

blueimp-load-imageは画像加工処理を行うためのnpmライブラリです。 画像加工、リサイズ、回転情報の削除、プレビュー画像の作成などを簡単に実装することができます。

github.com

さっそく実装を見ていきましょう。

import * as loadImage from 'blueimp-load-image';

const resizeImageWorker = (imageFile, options = {
  maxHeight: 828,
  maxWidth: 1104,
  canvas: true,
  orientation: true,
}) => {
  return new Promise((resolve) => {
    loadImage(imageFile, (canvas) => {
      const type = 'image/jpeg';
      const dataUri = canvas.toDataURL(type);
      const bin = atob(dataUri.split(',')[1]);
      const buffer = new Uint8Array(bin.length);
      for (let i = 0; i < bin.length; i++) {
        buffer[i] = bin.charCodeAt(i);
      }
      resolve(new Blob([buffer.buffer], { type: type }))
    }, options);
  });
}

function createProductImages(payload) {
  const { productRecipeId, productImage } = payload;
  return resizeImageWorker(productImage).then((blob) => {
    const params = new FormData();
    params.append('product_image', blob);

    // リサイズ後の画像を画像アップロードAPIサーバーに連携
    return axios.post(`/api/product_recipes/${productRecipeId}/product_images`,
      params, {
        headers: {
          'content-type': 'multipart/form-data',
        },
      });
  })
}

商品レシピの画像をリサイズして画像アップロードAPIに連携するというサンプルプログラムになります。

resizeImageWorkerという関数で画像リサイズ処理を行っています。

blueimp-load-imageからimportしたloadImageという関数を使用します。 loadImageは非同期処理なのでPromise化して、resolveしてBlobを返却するという作りにしています。

resizeImageWorkerで返却されたBlobを画像アップロードAPIに送信することで処理が完了します。

たった10行ほどのコードでリサイズ処理が実装できてしまいます。

他に候補に上がったライブラリ

画像リサイズ処理を実装するにあたり他にもいくつかライブラリを試してみました。

今回は要件が合わなくて使用を見送りましたが、使い勝手が良いライブラリばかりなのでチェックしてみると良いでしょう。

imtool

github.com

IE11はサポート対象外であるためNGとしました。

実際に導入したところAPIが分かりやすく使いやすかったです。 IEがサポート対象外であればこちらを使用していたと思います。

jimp

github.com

commonJS方式のみサポートなのでNGとしました。

commonJSをサポートしている環境であれば有りだと思います。

pica

github.com

commonJS方式のみサポートなのでNGとしました。

commonJSをサポートしている環境であれば有りだと思います。

画像アップロード時はクライアントサイドでも圧縮処理をすべき理由

画像アップロードする際に、最近ではクライアントサイドであらかじめサイズを圧縮しておくのが主流である。

スマホカメラの高性能化に伴い、写真がどんどん高画質になっているためである。 10MB近いサイズのこともあったりする。

画像アップロードのフローからみる改善点

スマホ ①=> サーバーサイド ②=> ファイルストレージ

上記の①のアップロード部分はクライアント側のネットワーク速度に依存するため、通信が不安定な状態だといつまでたってもアップロード処理が終わらない。 (10MBの画像を上り1Mbsの速度でアップロードすると10秒以上かかる)

②はサーバーサイドなので通信による影響はある程度制御できる。 安定した状態であれば10MB程度のアップロードは1秒未満で完了するためそれほど問題にはならない。

画像サイズを1/10の1MBまで圧縮できれば、①の処理は1秒程度で完了する。

それゆえにクライアントサイドで画像を圧縮した上でアップロードすることがとても重要なのである。

(保険としてサーバーサイドでも圧縮処理を掛けておくべきであるのは言うまでもない)

Reactのライフサイクル一覧まとめ

マウントフェーズ

componentWillMount

  • 基本的には非推奨
    • まずはconstructorかcomponentDidMountで制御できないか検討すること
    • v16からUNSAFE_componentWillMountという名前に変更
  • renderよりも前に呼ばれる
  • constructorよりも後に呼ばれる
  • setStateしてもrenderはされない

componentDidMount

  • コンポーネントがマウントされた直後に呼ばれる
  • DOMにアクセスできるため、イベントリスナー登録などはこのフェーズで行う
  • setStateは原則禁止
    • render → componentDidMount → setState → render
    • 無駄に2回描画されるので特別な理由がない限りは禁止

componentWillUnmount

  • コンポーネントがアンマウントされる直前に呼ばれる
  • setIntervalをクリアしたり、イベントリスナーを解除したりするフェーズ

アップデートフェーズ

componentWillReceiveProps(nextProps)

  • 基本的には非推奨
    • v16からUNSAFE_componentWillReceivePropsという名前に変更
  • setState可
  • propsの変更直前に呼ばれる
  • stateの変更では呼ばれない

shouldComponentUpdate(nextProps, nextState)

  • renderさせるかどうかを制御する
    • PureComponentを使用するほうが一般的
  • true/falseが返却値
    • trueの場合はrenderさせる
    • falseの場合はrenderさせない
  • 主にパフォーマンスチューニングのために使用する

componentWillUpdate(nextProps, nextState)

  • 基本的には非推奨
    • v16からUNSAFE_componentWillUpdateという名前に変更
  • renderされる直前の最後のフェーズ
  • setStateは禁止
    • componentWillUpdateが再度実行されて無限ループの可能性があるため
  • shouldComponentUpdateでfalseを返した場合は呼ばれない

componentDidUpdate(prevProps, prevState, snapshot)

  • 仮引数は前のprops、state
  • renderの直後に呼ばれる
  • setState可
  • shouldComponentUpdateでfalseを返した場合は呼ばれない