javascriptとrubyのクロージャーについて

クロージャーは「ローカル変数を参照する関数」です。

このいいところは、変数を一時保存できるところだと思ってます。

function closure() {
  let x = 0;
  return function(){
    return ++x;
  }
}

c = closure()
c()
=>1
c()
=>2

何回押したとか、初回だけとかの条件分岐をする場合は、使いやすいのではないでしょうか。

ちなみにrubyだと関数はブロック(Procオブジェクト)で表すことになります。

def counter
  x = 0
  -> {x += 1}
end

c = counter
c.call
=>1
c.call
=>2

こんな感じですね。

以上です。

javascriptの引数について

javascriptの引数についてです。

引数のチェックをjavascriptでは行ってくれません。

ES2015からは改善されているようです。

引数のチェックはしない

function showName(name) {
  console.log(name);
}

showName('foo');
=>foo

showName('foo', 'bar');
=>foo

showName();
=>undefined

これはrubyだと、引数が合わないよって怒ってくるところですね。

def show(name)
  name
end

[4] pry(main)> show('foo')
=> "foo"
[5] pry(main)> show('foo', 'bar')
ArgumentError: wrong number of arguments (given 2, expected 1)
from (pry):5:in `show'
[6] pry(main)> show()
ArgumentError: wrong number of arguments (given 0, expected 1)
from (pry):5:in `show'

引数を持つオブジェクト

argumentsが持っています。

argumentsオブジェクトを利用して、引数の数を確認することができます。

下のような感じですね。

function showName(name) {
  if (arguments.length !== 1) {
    console.log(`given ${arguments.length}, expected 1`)
  }else {
  console.log(name);
  }
}

可変長引数のように書く

可変長引数の場合は、配列で回していくことになります。

下のような感じになります。

function sum() {
  var result = 0;
  for(var i = 0, len = arguments.length; i < len; i++) {
    var tmp = arguments[i]
    if (typeof tmp !== 'number') {
      throw new Error('引数が数値ではありません');
    }
    result += tmp;
  }
  return result;
}
sum(1, 3, 5, 7, 9);

こういうのを見ると、rubyは簡潔に書ける言語だなって感じてしまう。

javascriptのスコープについて

javascriptのスコープについてです。

曖昧な知識のままだったので、まとめます。

ブロックスコープ

ブロックでのスコープという概念がありません。

{
  var i = 5;
}
console.log(i);
=>5

このように平気でアクセスできてしまいます。

rubyだとブロックの中の変数は外からは参照することができません。

Proc.new do
  x = 1
end
p x
=>NameError: undefined local variable or method `x' for main:Object

関数のスコープで制限する

そこで編み出された手法が、関数の中はスコープが存在するので、関数の中に閉じ込めるという手法です。

(function(){
  var i = 5;
  console.log(i);
}).call(this);

console.log(i)
=>Uncaught ReferenceError: i is not defined

先人の人たちはよくこんなことを思いついたものだ。

さすがにこの技ばかりでは可読性がないので、ES2015からは状況が変わりました。

let constでスコープを定める

{
  let i = 5;
  console.log(i);
}
console.log(i)
=>Uncaught ReferenceError: i is not defined

このようにスコープできるようになりました。

以上です。

javascriptの関数の書き方について

最近少しずつ、javascriptについても学習をしています。

当然ながら、rubyとは動きが違うのですが、違う部分を意識するためにも、ここに記載しておきます。

関数の作り方

function命令で定義する

標準的な書き方ですね。

function 関数名 { 処理を記入 }

function hello(name) {
  return `my name is ${name}`;
}
hello("mikami");
"my name is mikami"

Functionコンストラクターで作成する

これは特殊な書き方だと思います。

特にこれで書く利点もないようなので、普通は書かないほうがいいです。

ただ、こんな書き方もあるんだなということで、書いておきます。

var 変数名 = new Function(引数, 処理を記入)

var hello = new Function('name', 'return `my name is ${name}`');
hello('mikmi');
"my name is mikami"

関数リテラルで作成する

これもよく利用される書き方だと思います。

var 変数名 = function(引数, 処理)

var hello = function(name) {
  return `my name is ${name}`;
};
hello('mikami');
"my name is mikami"

アロー関数で定義する

ES2015から使用できるようになりました。

coffee script を使用している人は、アロー演算子は馴染みがあるかも。

var hello = (name) => {
  return `my name is ${name}`;
}
hello('mikami');
"my name is mikami"

function命令の処理

javascriptのfunction命令は静的解析で行われます。

そのため、次のようなコードは動きます。

hello('mikami');

function hello(name) {
  return `my name is ${name}`;
};

では、関数リテラルで表現している場合はどうなるでしょうか?

hello('mikami');

var hello = function(name) {
  return `my name is ${name}`;
};

Uncaught TypeError: hello is not a function(…)

関数リテラルの場合は、実行時に評価されています。

ここら辺はちょっとした注意事項だと思います。

以上です。

nginxのパフォーマンスを向上させてみる

前回のエントリーでパフォーマンスの測定ができるようになりました。

今回はnginxのパフォーマンス向上に努めます。

なお、サイトの事前調査では最大リクエスト数の処理数が70なので、限界までリクエストするようにします。

最初に設定前のファイルを見てみましょう。

nginx.conf

user www-data;
worker_processes 4;
pid /run/nginx.pid;

events {
        worker_connections 1024;
        # multi_accept on;
}

http {

        ##
        # Basic Settings
        ##

        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        # keepalive_timeout 10;
        # client_header_timeout 10;
        # client_body_timeout 10;
        # reset_timedout_connection on;
        send_timeout 10;
        types_hash_max_size 2048;
        # server_tokens off;

        # server_names_hash_bucket_size 64;
        # server_name_in_redirect off;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ##
        # SSL Settings
        ##

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;

        ##
        # Logging Settings
        ##

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        ##
        # Gzip Settings
        ##

        gzip on;
        gzip_disable "msie6";

        # gzip_vary on;
        # gzip_proxied any;
        # gzip_comp_level 6;
        # gzip_buffers 16 8k;
        # gzip_http_version 1.1;
        # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

        ##
        # Virtual Host Configs
        ##

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
}

worker数の設定

worker_processes 4;

結果

Concurrency Level:      69
Time taken for tests:   4.444 seconds
Complete requests:      69
Failed requests:        0
Total transferred:      2385261 bytes
HTML transferred:       2334546 bytes
Requests per second:    15.53 [#/sec] (mean)
Time per request:       4443.779 [ms] (mean)
Time per request:       64.403 [ms] (mean, across all concurrent requests)
Transfer rate:          524.18 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       20   32   5.5     32      39
Processing:   359 2457 1250.9   2577    4403
Waiting:      315 2410 1250.7   2529    4356
Total:        379 2489 1256.1   2608    4442

Percentage of the requests served within a certain time (ms)
  50%   2597
  66%   3101
  75%   3579
  80%   3835
  90%   4261
  95%   4327
  98%   4442
  99%   4442
 100%   4442 (longest request)

私の初期設定では4になっていました。

こちらをautoに切り替えて、nginxに判断させます。

autoの場合は、自動でコア数に応じてくれます。

lscpuでコア数がわかります。

lscpu
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                2
On-line CPU(s) list:   0,1
Thread(s) per core:    1
Core(s) per socket:    1
Socket(s):             2
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 45
Model name:            Intel(R) Xeon(R) CPU E5-2640 0 @ 2.50GHz
Stepping:              7
CPU MHz:               2499.998
BogoMIPS:              4999.99
Hypervisor vendor:     KVM
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              4096K
NUMA node0 CPU(s):     0,1

今回の場合は2ですね。

なぜ、4にしていたのだろうか・・・orz

結果

Concurrency Level:      69
Time taken for tests:   4.041 seconds
Complete requests:      69
Failed requests:        0
Total transferred:      2385261 bytes
HTML transferred:       2334546 bytes
Requests per second:    17.07 [#/sec] (mean)
Time per request:       4041.382 [ms] (mean)
Time per request:       58.571 [ms] (mean, across all concurrent requests)
Transfer rate:          576.38 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       21   33   5.7     33      46
Processing:   346 2222 1104.8   2259    3993
Waiting:      303 2175 1104.9   2215    3944
Total:        371 2255 1110.2   2292    4039

Percentage of the requests served within a certain time (ms)
  50%   2287
  66%   2794
  75%   3232
  80%   3401
  90%   3784
  95%   3933
  98%   4039
  99%   4039
 100%   4039 (longest request)

比較

Time per request:       4443.779 [ms] (mean)
Time per request:       64.403 [ms] (mean, across all concurrent requests)
Time per request:       4041.382 [ms] (mean)
Time per request:       58.571 [ms] (mean, across all concurrent requests)

わずかながらですが、早くなりました。

mulit accept

同時に受け入れるかどうかでの判定です。 こちらもonにしてあげましょう。

        multi_accept on;

結果

Concurrency Level:      69
Time taken for tests:   3.443 seconds
Complete requests:      69
Failed requests:        0
Total transferred:      2385261 bytes
HTML transferred:       2334546 bytes
Requests per second:    20.04 [#/sec] (mean)
Time per request:       3443.017 [ms] (mean)
Time per request:       49.899 [ms] (mean, across all concurrent requests)
Transfer rate:          676.55 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       22   33   5.8     36      39
Processing:   247 1815 979.8   1883    3402
Waiting:      200 1766 977.1   1836    3359
Total:        270 1848 985.3   1920    3441

Percentage of the requests served within a certain time (ms)
  50%   1919
  66%   2331
  75%   2694
  80%   2841
  90%   3207
  95%   3368
  98%   3441
  99%   3441
 100%   3441 (longest request)

どんどん早くなっていく。

Time per request:       3443.017 [ms] (mean)
Time per request:       49.899 [ms] (mean, across all concurrent requests)

こんな設定するだけでも、トータルのレスポンスの処理速度が1,000msも短縮できました。

わずかな設定でここまで差が出るとは・・・

ちゃんとサーバーの設定を見直すことは大事ですね。

Apache Benchでパフォーマンス測定する

サイト作成していて、負荷テストにはまっていました。

便利なパフォーマンス測定ができるものがありますね。

パフォーマンス測定

Apache Bench

こちらを使用すれば、サイトに負荷をかけてレスポンスの向上するのかが確かめることができます。

ab -n 100 -c 100 http://www.example.co.jp/

-nはTotalで送るリクエストになります。 -cは同時に送る数になります。

この場合は、100のリクエストを同時に100個投げるようになります。

実験結果を出すために、今回は50にしてみます。

本当はexample.comには送信していなくて、実験中のサイトになります。

ab -n 50 -c 50 http://www.example.co.jp/

実験結果は次のようになります。

Server Software:        nginx/1.6.2
Server Hostname:        example.com
Server Port:            80

Document Path:          /
Document Length:        33834 bytes

Concurrency Level:      50
Time taken for tests:   2.817 seconds
Complete requests:      50
Failed requests:        0
Total transferred:      1728450 bytes
HTML transferred:       1691700 bytes
Requests per second:    17.75 [#/sec] (mean)
Time per request:       2817.040 [ms] (mean)
Time per request:       56.341 [ms] (mean, across all concurrent requests)
Transfer rate:          599.19 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       51   61   4.0     63      64
Processing:   152 1236 680.3   1222    2753
Waiting:      126 1186 647.9   1192    2416
Total:        203 1297 683.1   1284    2816

Percentage of the requests served within a certain time (ms)
  50%   1284
  66%   1743
  75%   1809
  80%   2019
  90%   2261
  95%   2354
  98%   2816
  99%   2816
 100%   2816 (longest request)

リクエストがどのくらい成功しているかの判定

Complete requests:      50
Failed requests:        0

こちらになります。 今回の場合は、全部捌けていますね。

秒間捌けるリクエスト数

Requests per second:    17.75 [#/sec] (mean)

17アクセス捌いていたことになります。

リクエストの処理時間

Time per request:       56.341 [ms] (mean, across all concurrent requests)

こちらになります。

56msになります。

ベンチマーク取ることで、速度向上しているかがわかるようになります。

参考:

qiita.com

factory girlでnamespaceのあるモデルのfactoryを生成する

ActsAsTaggableOnのデータを生成したかったのですが、namespaceがあるために生成できませんでした。

そんな時の技です。

FactoryGirl.define do
  factory :tag, class: ActsAsTaggableOn::Tag do |f|
    f.name '脈あり'
  end
end

公式にもっといいのがありました。

Find factories registered for namespaced models by monkbroc · Pull Request #843 · thoughtbot/factory_girl · GitHub

FactoryGirl.define do
  factory :tag, class: ActsAsTaggableOn::Tag do
    name '脈あり'
  end
end

以上です。

factory girlのtraitで関連するモデルを作成するのを選択制にする

関連するモデルを作成する場合は、associationを使います。

ただ、必ずしも毎回そのモデルが必要になるのか?と言われると疑問が浮かびます。

なので、選択制にする方が自由度が高いと考えています。

taritは選択制にできるので、使い方を書きます。

trait

factoryの部分はこう書きます。

  factory :foo do
    title 'foo'
    body 'bar'
    user_id 1

    trait :with_user do
      association :user, factory: :user
    end

呼び出す側は、こう書きます。

create(:foo) { :foo, :with_user }

これで選択制にすることができます。

以上です。

routeのresourcesのnestした部分をnamespaceにする方法

nestしたresourcesだと、namespaceでcontrollerとviewのファイル置き場が別々になってしまって嫌です。

下のやつです。

resources :foo do
  resouces : bar
end

結果

        foo_bar_index GET    /foo/:foo_id/bar(.:format)                  bar#index
                      POST   /foo/:foo_id/bar(.:format)                  bar#create
          new_foo_bar GET    /foo/:foo_id/bar/new(.:format)              bar#new
         edit_foo_bar GET    /foo/:foo_id/bar/:id/edit(.:format)         bar#edit
              foo_bar GET    /foo/:foo_id/bar/:id(.:format)              bar#show
                      PATCH  /foo/:foo_id/bar/:id(.:format)              bar#update
                      PUT    /foo/:foo_id/bar/:id(.:format)              bar#update
                      DELETE /foo/:foo_id/bar/:id(.:format)              bar#destroy
            foo_index GET    /foo(.:format)                              foo#index
                      POST   /foo(.:format)                              foo#create
              new_foo GET    /foo/new(.:format)                          foo#new
             edit_foo GET    /foo/:id/edit(.:format)                     foo#edit
                  foo GET    /foo/:id(.:format)                          foo#show
                      PATCH  /foo/:id(.:format)                          foo#update
                      PUT    /foo/:id(.:format)                          foo#update
                      DELETE /foo/:id(.:format)                          foo#destroy

ここでbar controllerになるのが、いやでたまりません。

プロジェクトが大きくなるにつれて、ファイルの定義場所に苦労することになると思います。

scope moduleを使います。

scopeについて

そもそもrouteのscopeとはなんでしょうか?

・scopeのみの場合、URLのみ指定したパスになります。

            bar_index GET    /foo/bar(.:format)                          bar#index
                      POST   /foo/bar(.:format)                          bar#create
              new_bar GET    /foo/bar/new(.:format)                      bar#new
             edit_bar GET    /foo/bar/:id/edit(.:format)                 bar#edit
                  bar GET    /foo/bar/:id(.:format)                      bar#show
                      PATCH  /foo/bar/:id(.:format)                      bar#update
                      PUT    /foo/bar/:id(.:format)                      bar#update
                      DELETE /foo/bar/:id(.:format)                      bar#destroy

これにmoduleというのがあるので、つけてみます。

・scope: :moduleは、controllerのアクションのみをnamespaceにします。

            bar_index GET    /bar(.:format)                              foo/bar#index
                      POST   /bar(.:format)                              foo/bar#create
              new_bar GET    /bar/new(.:format)                          foo/bar#new
             edit_bar GET    /bar/:id/edit(.:format)                     foo/bar#edit
                  bar GET    /bar/:id(.:format)                          foo/bar#show
                      PATCH  /bar/:id(.:format)                          foo/bar#update
                      PUT    /bar/:id(.:format)                          foo/bar#update
                      DELETE /bar/:id(.:format)                          foo/bar#destroy

これを利用して解決します。

解決策

  resources :foo do
    scope module: :foo do
      resources :bar
    end
  end

結果

        foo_bar_index GET    /foo/:foo_id/bar(.:format)                  foo/bar#index
                      POST   /foo/:foo_id/bar(.:format)                  foo/bar#create
          new_foo_bar GET    /foo/:foo_id/bar/new(.:format)              foo/bar#new
         edit_foo_bar GET    /foo/:foo_id/bar/:id/edit(.:format)         foo/bar#edit
              foo_bar GET    /foo/:foo_id/bar/:id(.:format)              foo/bar#show
                      PATCH  /foo/:foo_id/bar/:id(.:format)              foo/bar#update
                      PUT    /foo/:foo_id/bar/:id(.:format)              foo/bar#update
                      DELETE /foo/:foo_id/bar/:id(.:format)              foo/bar#destroy
            foo_index GET    /foo(.:format)                              foo#index
                      POST   /foo(.:format)                              foo#create
              new_foo GET    /foo/new(.:format)                          foo#new
             edit_foo GET    /foo/:id/edit(.:format)                     foo#edit
                  foo GET    /foo/:id(.:format)                          foo#show
                      PATCH  /foo/:id(.:format)                          foo#update
                      PUT    /foo/:id(.:format)                          foo#update
                      DELETE /foo/:id(.:format)                          foo#destroy

scope moduleでfooのファイル置き場にするようにしています。 これをresourcesすることで求めていた結果になります。

以上です。

参考

qiita.com

railsの複数形・単数系がうまくいかない場合の対処

どう見てもこれは変換がおかしいだろってことがありました。

'loves'.singularize
=> "lofe"

普通にloveを返すべきところじゃん・・・

このおかげでpathがnew_lofe_pathなんてなったりしました。

これはどうにかしたいと思ったら、イレギュラー登録をすることで対応できました。

まずはこのメソッドの紹介します。

単数系→複数形

pluralize

"send".pluralize
=> "sends"

複数系→単数形

singularize

"sends".singularize
=> "send"

イレギュラーな変換を登録する

config/initializers/inflections.rb

# ActiveSupport::Inflector.inflections(:en) do |inflect|
#   inflect.plural /^(ox)$/i, '\1en'
#   inflect.singular /^(ox)en/i, '\1'
#   inflect.irregular 'person', 'people'
#   inflect.uncountable %w( fish sheep )
# end

コメントアウトされてサンプルが書かれていますね。

ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.irregular 'love', 'loves'
end

これで大丈夫になります。

以上です。