脳汁portal

アメリカ在住(だった)新米エンジニアがその日学んだIT知識を書き綴るブログ

Rails(ActiveRecord)に関して

Active Recordの値の取り扱いに関して

こんなレコードがあるとして

mysql> select id,name from users;
+----+---------+
| id | name    |
+----+---------+
|  1 | foo     |
|  2 | bar     |
|  3 | fizz    |
+----+---------+

これをActiveRecord経由で取得した場合どういうデータ形式になったりメソッドが使えるかという話

find_byだと

Userインスタンスが返ってくる
> User.select(:id, :name).find_by(id: 1)
=> #<User id: 1, name: "foo">
それぞれの値の取得方法
> user = User.select(:id, :name).find_by(id: 1)
=> #<User id: 1, name: "foo">

> user.id
=> 1
> user.name
=> "foo"

> user[:id]
=> 1
> user[:name]
=> "foo"
to_aメソッドやeachは使えない
> User.select(:id, :name).find_by(id: 1).to_a
Traceback (most recent call last):
        9: from script/rails:8:in `<main>'
        8: from script/rails:8:in `require'
        .
        .
        .

whereの場合

ActiveRecord::Relationが返ってくる
# ActiveRecord::Relationとして返ってくる
> User.select(:id, :name).where(id: [1,3])
=> #<ActiveRecord::Relation [#<User id: 1, name: "foo">, #<User id: 3, name: "fizz">]>
> User.select(:id, :name).where(id: [1,3]).class
=> User::ActiveRecord_Relation

# to_aにすると配列として返ってくる
> User.select(:id, :name).where(id: [1,3]).to_a
=> [#<User id: 1, name: "foo">, #<User id: 3, name: "fizz">]
irb(main):315:0> User.select(:id, :name).where(id: [1,3]).to_a.class
=> Array
それぞれの値の取得方法
# 配列のように指定ができる
> User.select(:id, :name).where(id: [1,3])[0]
=> #<User id: 1, name: "foo">
> User.select(:id, :name).where(id: [1,3])[1]
=> #<User id: 3, name: "fizz">

# その上で値が取得できる
> User.select(:id, :name).where(id: [1,3])[0].name
=> "foo"
> User.select(:id, :name).where(id: [1,3])[1].name
=> "fizz"
ActiveRecord::Relationでも配列でもeachが使える(取得できるのはuserインスタンス)
# ActiveRecord::Relation
> User.select(:id, :name).where(id: [1,3]).each do |user|
>     puts user
> end
#<User:0x00005560a6bd4d20>
#<User:0x00005560a6bd46b8>

# Array
> User.select(:id, :name).where(id: [1,3]).to_a.each do |user|
>     puts user
> end
#<User:0x00005560a6b3aef0>
#<User:0x00005560a6b3a6a8>

Vagrantで作成したVM環境にパスワード認証でsshログインする

まずはvagrant sshでログインする

# 設定変更
sudo vi /etc/ssh/sshd_config
### 以下のlineのnoをyesに変える ###
PasswordAuthentication no
### ここまで ###

# sshdのrestart
sudo systemctl restart sshd

これでteraterm等からパスワード認証でsshログインできるようになります

ubuntu16.04にrbenvでrubyをインストールする

# library install and update
sudo apt-get update
cd 
sudo apt-get install git build-essential libssl-dev libreadline-dev

vi ~/.profile
### 以下を末尾に追加 ###
export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"
### ここまで ###

source ~/.profile

# installできるversionのチェック
rbenv install --list

# install
rbenv install 2.5.1
rbenv rehash

# globalへの設定&確認
rbenv global 2.5.1
ruby -v

AWS EC2で”JavaScript heap out of memory”エラーが出たら

AWS EC2で”JavaScript heap out of memory”エラーが出たときの対処法です
主にt2.microとかメモリ容量の小さいインスタンスでnuxtとかnode使ってると出現する

こんなエラー

<--- Last few GCs --->

[5746:0x42f2e10]    11947 ms: Mark-sweep 101.0 (114.6) -> 100.2 (113.6) MB, 140.1 / 0.0 ms  (+ 0.3 ms in 1 steps since start of marking, biggest step 0.3 ms, walltime since start of marking 182 ms) (average mu = 0.294, current mu = 0.434) low memory notif[5746:0x42f2e10]    12099 ms: Mark-sweep 100.2 (113.6) -> 100.2 (113.6) MB, 152.0 / 0.0 ms  (average mu = 0.168, current mu = 0.000) low memory notification GC in old space requested

<--- JS stacktrace --->

==== JS stack trace =========================================

Security context: 0x1098ebb9e549 <JSObject>
    0: builtin exit frame: new ArrayBuffer(aka ArrayBuffer)(this=0x1ff267182801 <the_hole>,0x3c9d355d9001 <Number 6.41975e+06>,0x1ff267182801 <the_hole>)

    1: ConstructFrame [pc: 0x3e29d418d145]
    2: StubFrame [pc: 0x3e29d420b900]
    3: createUnsafeArrayBuffer(aka createUnsafeArrayBuffer) [0x3bbd5485d531] [buffer.js:119] [bytecode=0x365108798601 offset=25](this=0x1ff2671826f1 <undefined>,s...

FATAL ERROR: Committing semi space failed. Allocation failed - JavaScript heap out of memory
 1: 0x8c02c0 node::Abort() [node]
 2: 0x8c030c  [node]
 3: 0xad15de v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [node]
 4: 0xad1814 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [node]
 5: 0xebe752  [node]
 6: 0xecb0f2  [node]
 7: 0xecb2b4 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node]
 8: 0xecc7aa v8::internal::Heap::CollectAllAvailableGarbage(v8::internal::GarbageCollectionReason) [node]
 9: 0xae6cc5 v8::Isolate::LowMemoryNotification() [node]
10: 0x8bfdad node::ArrayBufferAllocator::Allocate(unsigned long) [node]
11: 0xfd88e3 v8::internal::JSArrayBuffer::SetupAllocatingData(v8::internal::Handle<v8::internal::JSArrayBuffer>, v8::internal::Isolate*, unsigned long, bool, v8::internal::SharedFlag) [node]
12: 0xb64514  [node]
13: 0xb664b3 v8::internal::Builtin_ArrayBufferConstructor(int, v8::internal::Object**, v8::internal::Isolate*) [node]
14: 0x3e29d41dc17d

対処法

sudo su
dd if=/dev/zero of=/swapfile bs=1M count=1024
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile

node.jsのインストール方法

# install ndenv
cd /usr/local/
git clone https://github.com/riywo/ndenv.git
chmod -R g+rwxXs /usr/local/ndenv/
mkdir /usr/local/ndenv/plugins
cd /usr/local/ndenv/plugins
git clone https://github.com/riywo/node-build.git
cat << "EOF" > /etc/profile.d/ndenv.sh
export NDENV_ROOT=/usr/local/ndenv
export PATH="$NDENV_ROOT/bin:$PATH"
eval "$(ndenv init -)"
EOF
source /etc/profile.d/ndenv.sh

# install node
ndenv install ${NODE_VERSION}
ndenv global ${NODE_VERSION}
ndenv rehash

Nuxt.jsで画像をwebpで圧縮する

library install

npm install --save gulp-webp

gulpfile.js

vi gulpfile.js

const webp = require('gulp-webp')

gulp.task('webp', function() {
  return gulp.src("./precompile/img/**/*.{svg,gif,png,jpg,jpeg}")
             .pipe(webp())
             .pipe(gulp.dest("./assets/img/"));
});

実行コマンド

gulp webp

流れとしてはprecompile/imgという名前の画像ファイル置き場を作成し、ここにjpgやpngやらの画像を配置する
gulp webpコマンドで上記の画像ファイルをwebpファイルに変換してassets/img以下に配置する
(その後は通常通りnuxt generateでdist以下に画像ファイルが配置される)

AWS lambdaとAPI GatewayとEC2を使って特定のURLにアクセスするだけでインスタンスを作成できるようにする

Lambdaの設定

まずは実際の処理部分をLambdaで設定します

関数の作成

関数の作成を選択して必要な情報を入力していきます。
今回ランタイムはPython2.7を選択しています。
f:id:portaltan:20190322115621p:plain

右下の関数の作成を選択すると以下のような画面が表示されます
f:id:portaltan:20190322115852p:plain

下にゆくと以下のように処理を入力するエリアが出てきます
f:id:portaltan:20190322115948p:plain

import boto3
from botocore.exceptions import ClientError
import json
import logging
import os
import datetime

AMIID=os.environ['AMIID']

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def create_userdata():
    str="""#!/bin/sh
date > /tmp/userdate.log
yum -y update
${PROCESS WHICH YOU WANNA DO}
echo "Finished UserData" > /tmp/userdata.log
"""

    return str

def create_name():
    now = datetime.datetime.now()
    return 'instance_from_lambda_' + now.strftime('%Y/%m/%d %H:%M') + '(UTC)'

def lambda_handler(event, context):
    ret={}

    ec2 = boto3.client('ec2',region_name='ap-northeast-1')
    try:
        res = ec2.run_instances(
                                ImageId=AMIID,
                                InstanceType=event['type'],
                                MinCount=1,
                                MaxCount=1,
                                InstanceInitiatedShutdownBehavior='terminate',
                                IamInstanceProfile={'Name': 'your IAM'},
                                NetworkInterfaces=[{
                                            'DeviceIndex': 0,
                                            'SubnetId' : 'your subnet id',
                                            'Groups': ["your security group"],
                                            'AssociatePublicIpAddress': True            
                                        }],
                                TagSpecifications=[
                                            {
                                                'ResourceType':'instance',
                                                'Tags':[
                                                    {
                                                    'Key':'Name',
                                                    'Value':create_name()
                                                    }
                                                ]
                                            }
                                        ],
                                UserData=create_userdata()
                                )
        
        return res['Instances'][0]['InstanceId']
    except ClientError as e:
        ret['code'] = e.response['Error']['Code']
        ret['message'] = e.response['Error']['Message']
        
        raise json.dumps(ret)    
  • 今回はAMIIDは以下のように環境変数で設定します

f:id:portaltan:20190322133447p:plain

  • instance typeはAPI Gatewayのステージ変数で設定します
  • IAMとsubnet id、security groupは自分の環境に応じたものを入力してください

次にタイムアウトの時間を3秒から15秒に伸ばしておきます
f:id:portaltan:20190322134310p:plain

最後に右上の保存を押して設定を保存します

test

次にまずはLambda単体の設定がうまくいっているかどうかテストをします。
保存の隣にあるテストを選択し、テストに必要なインスタンスタイプを入力します
f:id:portaltan:20190322134519p:plain

次に実際にテストを実行します。うまくいくと次のような表示が現れ、responseとして起動したインスタンスIDが返ってきます。
f:id:portaltan:20190322134625p:plain

以上でLambda側の設定を完了します

API Gatewayの作成

次に上記のLambdaの処理を呼び出すAPIを作成します

リソースの作成

API Gateway > APIの作成を選択し、必要な項目を入力してAPIの作成を選択していきます
f:id:portaltan:20190322114008p:plain

以下のような画面になります
f:id:portaltan:20190322114055p:plain

アクションからリソースの作成を選択し、必要な情報を入力します
f:id:portaltan:20190322114212p:plain

次にステージ変数としてインスタンスタイプを送れるようにします
f:id:portaltan:20190322135343p:plain

次にアクションからメソッドの作成を選択し、ドロップダウンでGETを選択します。
選択すると具体的な設定の入力項目が出現するので、今回は統合タイプは「Lambda関数」、Lambda関数には先ほど作成した関数の「test」を入力します
f:id:portaltan:20190322134752p:plain

次に統合リクエストのマッピングテンプレートから、パラメータの設定を行います。
f:id:portaltan:20190322140803p:plain

{
  "type": "$input.params('type')"
}

APIのデプロイ

作成したAPIはまだデプロイしていないので、明示的にデプロイする必要があります。
以下のようにアクションからAPIのデプロイを選択し、名前をつけてデプロイします

  • 今回はtest_deployとした

f:id:portaltan:20190322134917p:plainf:id:portaltan:20190322134941p:plain

ステージの確認

ステージという項目から、先ほどdeployしたAPIのアクションを確認できます。
GETを選択すると、以下のようにAPIのエンドポイントとパラメーター等を確認できます

Lambdaの確認

先ほどのLambdaの画面に戻ると、トリガーの項目にAPI Gatewayが設定されていることが確認できます。

以上でAPIの設定は完了です

確認

ブラウザから作成したAPIを呼び出してみます
https://${your env}.execute-api.ap-northeast-1.amazonaws.com/test_deploy/create/c5.xlarge

  • 今回はインスタンスタイプとしてc5.xlargeを指定しています
  • ${your env}の部分は自分の環境に応じて上記で確認したエンドポイントを入力してください