shallow copy と deep copy

こんにちは!kossyです!




さて、今回はshallow copydeep copy の違いについて、
ブログに残してみたいと思います。
なお、例はRubyで書いていきます。



shallow copy

しっくりくる説明を引用させて頂きました。

シャローコピー(浅いコピー)はプリミティブ値(文字列、数値、真偽値、null、undefined、Symbol)をコピーするが、
それ以外のオブジェクトは参照をコピーする。参照がコピーされるということは、コピー元とコピー先でオブジェクトが共有されるということである。
一方、ディープコピー(深いコピー)はプリミティブ値だけでなく、オブジェクトも値としてコピーする。したがって、コピー元とコピー先のオブジェクトは別物である。

出典: https://nansystem.com/shallow-copy-vs-deep-copy/

この挙動をRubyのコードで試してみます。

Rubyには、shallow copyを実現するメソッドが2つあります。

# dup

irb(main):001:0> str = 'base'
=> "base"
irb(main):002:0> dupped_str = str.dup
=> "base"
irb(main):003:0> str == dupped_str
=> true
irb(main):004:0> str === dupped_str
=> true
irb(main):005:0> str.object_id == dupped_str.object_id
=> false
irb(main):006:0> str.object_id
=> 70127702447420
irb(main):007:0> dupped_str.object_id
=> 70127702479340



# clone

irb(main):001:0> str = 'base'
=> "base"
irb(main):002:0> cloned_str = str.clone
=> "base"
irb(main):003:0> str == clone_str
=> true
irb(main):004:0> str === clone_str
=> true
irb(main):005:0> str.object_id == cloned_str.object_id
=> false
irb(main):006:0> str.object_id
=> 70203614857500
irb(main):007:0> cloned_str.object_id
=> 70203610561780

文字列をコピーする場合は、異なるオブジェクト(参照するメモリ領域も異なるもの)として定義しています。


次に、Arrayを例にしてみます。

irb(main):001:0> arr = ['one', 'two']
=> ["one", "two"]
irb(main):002:0> dupped_arr = arr.dup
=> ["one", "two"]
irb(main):003:0> dupped_arr.first.upcase!
=> "ONE"
irb(main):004:0> arr
=> ["ONE", "two"] <= !!!
irb(main):005:0> dupped_arr
=> ["ONE", "two"]
irb(main):006:0> arr.object_id
=> 70260964042220
irb(main):007:0> dupped_arr.object_id
=> 70260967711360


irb(main):001:0> arr = ['one', 'two']
=> ["one", "two"]
irb(main):002:0> cloned_arr = arr.clone
=> ["one", "two"]
irb(main):003:0> cloned_arr.first.upcase!
=> "ONE"
irb(main):004:0> arr
=> ["ONE", "two"]
irb(main):005:0> cloned_arr
=> ["ONE", "two"]
irb(main):006:0> arr.object_id
=> 70296824577020
irb(main):007:0> cloned_arr.object_id
=> 70296816415960


なぜこのような挙動になるのかは

に書いてありました。

clone や dup はオブジェクト自身を複製するだけで、オブジェクトの指している先(たとえば配列の要素など)までは複製しません。

出典: https://docs.ruby-lang.org/ja/latest/method/Object/i/clone.html

なるほど、だから破壊的変更がarrにも反映されてしまっていたのですね、、、



deep copy

deep copyshallow copyとはオブジェクトの指している先までコピーする点で異なります。

Rubydeep copyを実現する場合、Marshalクラスを用いることで実現できますが、
Railsを使って開発をしている場合は、deep_dupメソッドを用いることでも実現ができます。

[1] pry(main)> arr = ['one', 'two']
=> ["one", "two"]
[2] pry(main)> deep_dupped_arr = arr.deep_dup
=> ["one", "two"]
[3] pry(main)> deep_dupped_arr.first.upcase!
=> "ONE"
[4] pry(main)> arr
=> ["one", "two"]
[5] pry(main)> deep_dupped_arr
=> ["ONE", "two"]
[6] pry(main)> arr.object_id
=> 70258270856240
[7] pry(main)> deep_dupped_arr.object_id
=> 70258261330960

deep_dupして生成したオブジェクトがコピー元のオブジェクトに影響を与えていないことが確認できました。




勉強になりました。

Effective Ruby 第2章まとめ

こんにちは!kossyです!




さて、今回はEffective Rubyの第2章の「覚えておくべき事項」をまとめたものを、
ブログに残してみたいと思います。


第1章のまとめ記事はこちら




第2章 クラス、オブジェクト、モジュール

2-1. Rubyが継承階層をどのように組み立てるかを頭に入れよう

  • Ruby は、クラス階層をサーチするだけでメソッドを見つけられる。探しているメソッドが見つからないときには、method_missing を探して再びサーチを開始する
  • モジュールをインクルードすると、暗黙のうちに特異クラスが作られ、その特異クラスはクラス階層のインクルードしたクラスの上に挿入される
  • 特異メソッド(クラスメソッドと特定のオブジェクト専用のメソッド)は、同じようにクラス階層に挿入される特異クラスに格納される

2-2. superのふるまいがひと通りではないことに注意しよう

  • 継承階層の上位にあるメソッドをオーバーライドするときには、superキーワードを使ってオーバーライドされるメソッドを呼び出すことができる
  • 引数も括弧を付けずにsuperを呼び出すと、呼び出し元のメソッドに渡された全ての引数を渡してオーバーライドされるメソッドを呼び出すのと同じ意味になる
  • オーバーライドされるメソッドに引数を渡さずにsuperを使いたい場合には、super()のように空の括弧を使わなければならない
  • method_missingメソッドを定義してしまうと、super呼び出しが失敗した時に得られる便利な情報が失われてしまう

2-3. サブクラスを初期化する時にはsuperを呼び出そう

  • Rubyは、サブクラスのオブジェクトを作る時に、スーパークラスのinitializeメソッドを自動的に呼び出したりはしない。initializeにも通常のメソッドルックアップの規則が適用され、最初に見つかったバージョンが実行される
  • 明示的に継承を使うクラスでinitiazeメソッドを書く時には、superを使って親クラスを初期化しなければならない。initialize_copyメソッドを定義する時にも、同じ規則が当てはまる

2-4. Rubyの最悪に紛らわしい構文に注意しよう

  • セッターメソッドは、明示的にレシーバを指定しなければ呼び出せない。レシーバがなければ、変数への代入と解釈されてしまう
  • インスタンスメソッドからセッターメソッドを呼び出す時には、レシーバとしてselfを使う
  • セッター以外のメソッドを呼び出す時には明示的にレシーバを指定する必要はない。selfでコードを汚さないようにしよう

2-5. 構造化データの表現にはHashではなくStructを使おう

  • 新しいクラスを作るほどでもない構造化データを扱う時には、HashではなくStrictクラスを使うようにしよう
  • Strict::newの戻り値を定数に代入し、その定数をクラスのように扱おう。

2-6. モジュールにコードをネストして名前空間を作ろう

  • モジュール内に定義をネストして名前空間を作ろう
  • 名前空間の構造は、ディレクトリ構造と同じにしよう
  • トップレベル定数を修飾無しで使うと曖昧になる時には、::を使ってフルに修飾しよう

2-7. さまざまな等値の違いを理解しよう

  • equal?メソッドをオーバーライドしてはならない。このメソッドは、厳格にオブジェクトを比較し、両方がメモリ内の同じオブジェクトを指すポインタ出ない限り(つまり、両者がともに同じobject_idを持たない限り)trueを返さないという動作をすべきものと考えられている
  • Hashクラスは、衝突が起きたときに、キーとして使われているオブジェクトを比較するためにeql?メソッドを使っている。デフォルト実装は、おそらく望ましい動作をしない
  • 2つのオブジェクトが同じ値かどうかをテストするときには == 演算子を使う。数値を表すクラスなど、一部のクラスは型変換を実行する緩やかな等値演算子を持っている
  • case式は、個々のwhen節をテストするために === 演算子を使っている。左被演算子はwhenに与えられる引数、右被演算子はcaseに与えられる引数である

2-8. <=>とComparableモジュールで比較を実装しよう

  • オブジェクトの順序は、<=>演算子を定義し、Comparableモジュールをincludeして実装しよう
  • <=>演算子は、左被演算子が右被演算子と比較できないものならnilを返す
  • クラスのために<=>を実装した場合、特にインスタンスをハッシュキーとして使うつもりなら、eql?を==の別名にすることを検討しよう。別名にする場合は、hashメソッドもオーバーライドしなければならない。

2-9. protectedメソッドを使ってプライベートな状態を共有しよう

  • プライベートな状態はprotectedメソッドで共有する。関連するクラスの間でプライベートな情報を共有する目的がある
  • レシーバを明示してprotectedメソッドを呼び出せるのは、同じクラスのオブジェクトか共通のスーパークラスからprotectedメソッドを継承しているオブジェクトだけだ

2-10. クラス変数よりもクラスインスタンス変数を使うようにしよう

  • クラス変数よりもクラスインスタンス変数を使うようにしましょう
  • クラスはオブジェクトなので、専用のプライベートなインスタンス変数セットを持っている。


出典: https://www.shoeisha.co.jp/book/detail/9784798139821

Rails.application.configureで環境毎にcorsのallow_originsを設定する

こんにちは!kossyです!



さて、今回はRails.configurationに環境毎にoriginを定義して、
corsのoriginの設定を行う方法について、ブログに残してみたいと思います。




環境
Ruby 2.6.3
Rails 6.0.3
MacOS Mojave



corsとは?

みんな大好きMDNのcorsの解説を引用します。

Cross-Origin Resource Sharing (CORS) は、
追加の HTTP ヘッダーを使用して、あるオリジンで動作しているウェブアプリケーションに、
異なるオリジンにある選択されたリソースへのアクセス権を与えるようブラウザーに指示するための仕組みです。
ウェブアプリケーションは、自分とは異なるオリジン (ドメインプロトコル、ポート番号) にあるリソースをリクエストするとき、
オリジン間 HTTP リクエストを実行します。

オリジン間リクエストの一例: https://domain-a.com で提供されている
ウェブアプリケーションのフロントエンド JavaScript コードが
XMLHttpRequest を使用して https://domain-b.com/data.json へリクエストを行う場合。

セキュリティ上の理由から、ブラウザーは、スクリプトによって開始されるオリジン間 HTTP リクエストを制限しています。
例えば、 XMLHttpRequestや Fetch API は同一オリジンポリシーsame-origin policyに従います。
つまり、これらの API を使用するウェブアプリケーションは、
そのアプリケーションが読み込まれたのと同じオリジンに対してのみリソースのリクエストを行うことができ、
それ以外のオリジンの場合は正しい CORS ヘッダーを含んでいることが必要です。

出典: https://developer.mozilla.org/ja/docs/Web/HTTP/CORS

要はリクエスト元のオリジン(ドメイン + プロトコル + ポートを含めたもの)とリクエスト先のオリジンが異なる時に、
異なるオリジンにあるデータにアクセスしたいって時にブラウザに許可を与える仕組みです。

例えば、APIのエンドポイントURLとフロントエンドアプリのURLが異なっている場合、
corsへの対応が必要になります。

RailsAPIに徹して、Vue.jsやReactはRailsから受け取ったJSONを加工して表示するという構成のWebアプリがあれば、
cors対応を行うことになるかと思います。


Rails.application.configureでユーザー定義値を設定する

Railsで環境毎にグローバルにユーザー定義値を設定する場合、

config/environments/**.rb内で、

Rails.application.configure do
  # 省略

  config.define_user_value = 'value'

end

のように定義すると、

$ Rails.configuration.define_user_value
=> "value"

のようにどこからでも呼び出せるようになります。


この手法を応用して、corsのoriginsの値を定義していきます。

originsの設定

config/environments/**.rbに以下を定義します。

Rails.application.configure do
# 省略
  config.allow_origins = ['https://allow_origin_url.co.jp', 'https://second_allow_origins_url.com']
end

次にconfig/initializers/cors.rbを以下のように編集します。

Rails.application.config.middleware.use Rack::Cors do
  allow do
    origins Rails.configuration.allow_origins
    resource '*',
    :headers => :any,
    :expose  => ['access-token', 'expiry', 'token-type', 'uid', 'client'],
    :methods => [:get, :post, :options, :delete, :put]
  end
end

このように設定することで、環境毎に許可するoriginsを設定することができます。




勉強になりました。

DockerでAngular10の環境を構築してみる

こんにちは!kossyです!



さて、今回はDockerでAngularの環境を構築する手順について、
ブログに残してみたいと思います。




環境
Docker 19.03.12
docker-compose 1.26.2
Docker Desktop for Mac 2.3.0.4



Dockerfile作成

何はともあれまずはDockerfileを作成します。
コンテナ内でAngularアプリのボイラープレートを作るためだけに使用しますので、
中身はnodeを入れてangular-cliをnpm installするだけにしています。

// 作業ディレクトリ作成

$ mkdir angular-sample

$ cd angular-sample

// dockerfile作成

angular-sample$ touch Dockerfile

Dockerfile

FROM node:12.18
RUN npm install -g @angular/cli

コンテナ内でAngularアプリ作成

次にコンテナ内に入ってAngularアプリの雛形を作成していきます。

angular-sample$ docker run -it -v $PWD:/myapp -p 4200:4200 angular-sample bash

root@コンテナID:/$cd myapp

root@コンテナID:/$ng new angular-sample

? Would you like to add Angular routing? (Y/n)  // どちらでもいいです

? Which stylesheet format would you like to use? // これもどちらでもOK

この操作でローカルの作業ディレクトリ内にangular-sampleが作成されれば成功です。


開発環境用のDockerfile・Docker-compose.yml を作成

次に開発環境用のDockerfileを作成します。先ほど作ったDockerfileの中身を修正していきます。

FROM node:12.18
ENV LANG en_US.UTF-8

RUN npm install -g @angular/cli
RUN apt-get update -qq && apt-get install -y vim

RUN mkdir /myapp
WORKDIR /myapp

ENV LANG en_US.UTF-8文字コード変換のエラーの発生を防ぐ意図があります。
Docker: コンテナのlocaleを設定したい - Qiita

vimは何かと便利なので入れています。その他は作業ディレクトリを指定する以外は
特に差分はありません。

次にdocker-compose.ymlを作成します。

angular-sample $ touch docker-compose.yml

./docker-compose.yml



version: '3'

services:
  node:
    build: .
    ports:
      - "4200:4200"
    volumes:
      - ".:/myapp"
    tty: true

ポート番号はangularのデフォルトで採用されている4200番を用いています。
他アプリとぶつからないように変更するのもいいと思います。

あとはdocker-compose buildをして、docker-compose upします。

angular-sample $ docker-compose build

angular-sample $ docker-compose up

もう一つターミナルを開いて、以下のコマンドでコンテナ内に入り、ng serveします。

angular-sample $ docker-compose exec node bash 

nodeはdocker-compose.ymlで指定したservices名で、コンテナ内に入るためにbashを指定しています。


あとは、コンテナ内で ng g component 等のコマンドを叩いて開発するも良し、
docker-compose run node ng g component とローカルで叩いて開発するも良しです。




今回作成したDockerfileやdocker-compose.ymlは

https://github.com/kossy0701/angular-sample

に置いておきました。開発の際に参考になれば幸いです。



勉強になりました。

ng build時に javascript heap out of memory が発生した場合の対処法

こんにちは!kossyです!



さて、今回はAngularでng build を行った際に、
javascript heap out of memory」エラーが発生し、
buildが通らない場合の対処法について、
ブログに残してみたいと思います。



環境
Angular CLI: 9.1.7
Node: 12.13.1
typescript 3.8.3
webpack 4.43.0



発生する原因

Javascriptのヒープメモリが逼迫した時に発生してしまうようです。

ではヒープメモリとは何でしょうか。


『ヒープ領域』は、アプリケーションやOSで動的に割り当てたり解放するものなんだ。プログラムで一時的に必要になるメモリで、例えばファイルを読み出すときに読みだしたファイル内容を置いておいたり、ネットワークでデータを送受信する時にデータを置いておく時に使うよ。

必要な時に動的に確保し、使い終わったら解放することで、限られたメモリを有効に使うことができるね。malloc()という関数でメモリを割り当て、free()という関数で解放するのが標準的な方法なんだ。

出典: https://www.uquest.co.jp/embedded/learning/lecture16.html

なるほど、ヒープメモリはヒープ領域とも言うんですね。
プログラム内で一時的に必要になるメモリの上限を突破してしまったということでしょう。
なので、メモリの上限を上げてあげればなんとかなりそうです。

ng buildの前にオプションを渡す

javascript heap out of memory エラーが発生してbuildに失敗した場合は、

$ NODE_OPTIONS="--max-old-space-size=1024" ng build

space-sizeは2048, 4096 のように よしなに設定してもらえればOKです。


参考: https://blog.gaji.jp/2019/12/17/1860/



勉強になりました。

RubyのNKFモジュールとFileクラスとPathnameクラスを使ってファイルの文字コードを調べる

こんにちは!kossyです!




さて、今回はRubyNKFモジュールとFileクラスとPathnameクラスを使って、
ファイルの文字コードを調べる方法にについて、ブログに残してみたいと思います。



環境

Ruby 2.6.3
Rails 6.0.3
MacOS Mojave



手順

大まかな手順は以下の通りです。

1. pathnameクラスを使ってfile_pathを定義

2. Fileクラスのreadメソッドでファイルを読み込む

3. NKFクラスのguessメソッドで文字コードを調べる

です。ではコンソールで試してみます。


/tmpディレクトリに適当なファイルを配置します。
今回は、sample.txt を配置

> file_path = Pathname.new('./tmp/sample.txt')

> file = File.read(file_path)

> require 'nkf'

> NKF.guess(file).to_s 
=> 'UTF-8'

上記のようなやり方で、文字コードを確認できます。



ユースケース

実務で使うとしたら、アップロードされようとしているCSVデータの文字コードによって処理を分岐する、といった使い方があるかと思います。

require 'nkf'

file_path = Pathname.new('./tmp/sample.csv')

csv_file =
  if NKF.guess(File.read(file_path)).to_s == 'Shift_JIS'
     CSV.read(file_path, encoding: 'cp932')
  else
    CSV.read(file_path)
  end

勉強になりました。

参考にさせていただいたサイト



TypeScriptの便利機能の紹介(その1)

こんにちは!kossyです!


さて、今回はTypeScriptの便利機能をブログに残してみたいと思います。

もくじ

  • Intersection Types
  • Type Guard
  • タグ付きUnion
  • アサーション
  • NonNullAssertionOperator

InterSection Types

交差型ともいい、二つの型の積集合を取る。
ちなみに、合併型はUnion型ともいい、二つの型の和集合を取る。

コード例

type Sales = {
  retail: string;
};

type Developer = {
  dev: string;
};

type SalesDeveloper = Sales & Developer // 合併(Union型なら & の 代わりに | を用いる)

const superman: SalesDeveloper = {
  retail: 'super',
  dev: 'VPoE'
};

以下のように必須のプロパティを全て用意しない場合、VSCodeで怒られる。

const superman: SalesDeveloper = {
  retail: 'super'
};

f:id:kossy-web-engineer:20200901234850p:plain

Type Guard

JavaScriptに元々存在する、データ型を返す演算子を使って、型を絞り込むことができる

  • typeof
  • in
  • instanceof

コード例

typeof

arg = 1;

toFixed(arg: string | number) {
  if (typeof arg === 'number') {
    arg.toFixed();
  }
};




in

type Sales = { retails: string, name: string };

type Developer = { dev: string, name: string };

type SalesDeveloper = Sales | Developer;

function showProfile(salesDeveloper: SalesDeveloper) {
  if ('dev' in salesDeveloper) { // devというプロパティがあれば
    console.log(salesDeveloper.dev);
  }
}



instanceof

class Sales {
  name: string;
  retail: string;
};

class Developer {
  name: string;
  dev: string;
};

type SalesOrDeveloper = Sales | Developer;

function isSales(salesOrDeveloper: SalesOrDeveloper): boolean {
  if (salesOrDeveloper instanceof Sales) { // 引数のsalesOrDeveloperがSalesクラスのインスタンスだった場合
     return !!salesOrDeveloper.retail;
  } else {
    return !!salesOrDeveloper.dev;
  }
};

タグ付きUnion(Tagged Union Types・タグ付き共用体・discriminated Union)

switch文で分岐するといった使い方ができる

class Sales {
  kind: 'sales' = 'sales';  // タグ
  name: string;
  retail: string;
};

class Developer {
  kind: 'developer' = 'developer'; // タグ
  name: string;
  dev: string;
};

type SalesDeveloper = Sales | Developer;

function showYourWork(salesDeveloper: SalesDeveloper): string {
  switch (salesDeveloper.kind) {
  case 'sales':
    return salesDeveloper.retail;
  case 'developer':
    return salesDeveloper.dev;
  }
};

アサーション

手動で型を上書きする方法
as構文か<>構文(山括弧とかangle bracketsともいう)で記述する。

class Sales {
  kind: 'sales' = 'sales';
  name: string;
  retail: string;
};

class Developer {
  kind: 'developer' = 'developer';
  name: string;
  dev: string;
};

type SalesDeveloper = Sales | Developer;

const sales: SalesDeveloper = { kind: 'sales', name: 'john' } as Sales;

const sales: SalesDeveloper = <Sales>{ kind: 'sales', name: 'john' };



<>構文だとtslintで怒られる、、、

Type assertion using the '<>' syntax is forbidden. Use the 'as' syntax instead.
<> 構文を使用した型アサーションは禁止されています。 代わりに「as」構文を使用してください。

NonNullAssertionOperator

!を使って string | null のようになっている型を持つ変数等に対して、
「この変数はnullではない」と明示的に示す方法

const input = document.getElementById('input')!;

tslintだと!は使うなって怒られる、、、
Forbidden non null assertion (no-non-null-assertion)


続きます。

AngularのPLATFORM_IDのユースケース

こんにちは!kossyです!



さて、今回は個人開発アプリでお世話になっているangular-tokenというnpmライブラリのソースコードを読んでいる時に見つけた、
PLATFORM_IDについて、これが何なのかについてと、ユースケースについてもブログに残してみたいと思います。

f:id:kossy-web-engineer:20200829005053p:plain

A token that indicates an opaque platform id.

Google翻訳先生に訳してもらったところ、

不透明なプラットフォームIDを示すトークン。

とのこと。

不透明なって何やねん。と思ってopaqueを英和辞書で引いてみましたが、

どうやら「曖昧な」とか「不明瞭な」という意味があるみたい。

プラットフォーム = 実行環境だとすると、実行環境次第で値が決まるから「不明瞭なトークン」っていう説明になっているんでしょう。多分。


使い方

PLATFORM_IDは実行環境によって返り値が異なります。

ブラウザの場合は「browser」
サーバーの場合は「server」が返り値になります。

なので、「実行環境によって処理を分岐したい」みたいなときに役立ちます。


以下、実装例です。

src/app.component.ts



import { Component, Injectable, Optional, Inject, PLATFORM_ID } from '@angular/core';
import { isPlatformServer } from '@angular/common';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})

export class AppComponent {
  constructor(
    @Inject(PLATFORM_ID) private platformId: Object,
  ) {}

  showPlatform(): string {
    if (isPlatformServer(this.platformId)) {
      return 'サーバー';
    } else {
      return 'ブラウザー';
    }
  }
}



src/app.component.html

<p>あなたの実行環境は{{showPLatform()}}です。</p>

f:id:kossy-web-engineer:20200830101404p:plain


実践的な使い方としては、実行環境がブラウザの場合だけwindowオブジェクトを用いた処理をする、
みたいな感じでしょうか。



勉強になりました。



参考にさせて頂いた記事
AngularUniversalでクライアントのみ(サーバのみ)実行させる方法 - Qiita
Angular 日本語ドキュメンテーション

AngularでBase64エンコードされた画像を表示したい

こんにちは!kossyです!



さて、今回はAngularでSafeResourceUrlインターフェイスを用いて、Base64エンコードされた画像を表示する方法を
ブログに残してみたいと思います。




環境
node 12.13.1
npm 6.14.4
Angular 9.1.7


Angularでは、信頼できないリソースだと判定された場合、srcにunsafe:を付与する機能があります。
脆弱性対応がデフォルトで行われるのが理由なのですが、開発者側は、「このリソースは安全である」と
明示的に宣言しなければならない不便さがあります。

信頼できないリソースだと判定された場合の検証ツールのwarning
f:id:kossy-web-engineer:20200828231526p:plain

この「リソースが安全であると明示する」には、SafeResorceUrlインターフェイスを用いる必要があります。

以下、実装例です。

src/app/app.component.ts


import { Component, OnInit } from '@angular/core';
import { SafeResourceUrl, DomSanitizer } from '@angular/platform-browser';
import { SampleService } from './services/sample.service'

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  dataUrl: SafeResourceUrl;

  // Base64エンコードされた画像を取得するservice
  constructor(
    private sampleService: SampleService,
    private domSanitizer: DomSanitizer
  ) {
    this.sampleService.getImage().subscribe(data => { // dataの中身はBase64エンコードされたデータを想定。"data:image/png;base64,......"
      this.dataUrl = this.domSanitizer.bypassSecurityTrustResourceUrl(data);
    }
  }
}


src/app/app.component.html

<img [src]='dataUrl'>


warningが出ずに問題なく表示されれば成功です。

Railsのscope内でfind_byを使った場合の挙動がヤバかったwwww

こんにちは!kossyです!



さて、今回はRailsのscope内でfind_byを使った場合の挙動について、
ブログに残してみたいと思います。

なお、タイトルはYoutubeのタイトルを意識してみました。特に意味はありません。



環境

Ruby 2.6.3
Rails 6.0.3
MacOS Mojave



scopeとは?

まずは公式Docの引用

scope(name, body, &block)

Adds a class method for retrieving and querying objects.
The method is intended to return an ActiveRecord::Relation object,
which is composable with other scopes.
If it returns nil or false, an all scope is returned instead.

A scope represents a narrowing of a database query,
such as where(color: :red).select('shirts.*').includes(:washing_instructions).

オブジェクトを取得および照会するためのクラスメソッドを追加します。
このメソッドは、他のスコープで構成可能なActiveRecord :: Relationオブジェクトを返すことを目的としています。
nilまたはfalseを返す場合は、代わりにallスコープが返されます。

scopeをモデルに定義すると、
Article.your_define_scope のように呼び出せるようになります。

同じようなActiveRecord文をメソッドのようにまとめたり、controllerに書きがちな複雑なActiveRecord文をモデルに寄せることができます。


nilまたはfalseを返す場合、allが実行される

さて、このscopeメソッドですが、nilまたはfalseが返り値になる場合、
all(SELECT `table_names`.* FROM `table_names`)が実行されるという挙動になります。

つまり、

scope :probably_all_scope, -> (name) {
  find_by: name
}

のようにnilが返り得るscopeを定義してしまうと、
allメソッドが実行される可能性があります。



公式Docにきちんと書いてありますので、ライブラリ内部に定義済みのメソッドを使うときは、
まずはDocを読んで挙動を把握するようにしてから使いましょう。