小さなエンドウ豆

まだまだいろいろ勉強中

複数 docker-compose 間でネットワークを共有する方法

複数 docker-compose 間でネットワークを共有する方法

方法としては世の中に出回っているがちょっとつまずいたのでメモ程度にまとめる。

用途としてはサーバーとフロントエンドの環境をそれぞれ docker-compose で組み立てたがその動作確認がしたいときなどに使える。

手順

1. docker network 作成

# 作成
$ docker network create my_network

# 確認
docker network ls
709b835bca33        my_network       bridge              local

2. docker-compose.yml でネットワークを指定

docker-compose.yml その1

version: '3'
services:
  app:
    container_name: app
    build:
        context: ./
        dockerfile: ./Dockerfile
    volumes:
      - ./:/app
      - /node_modules/
    ports:
      - 3333:3333
    environment:
      - HOST=0.0.0.0
    command:
      yarn dev
    networks:
      - my_network

networks:
  my_network:
    external: true

yaml ファイルのトップレベルに networks を指定してあげる。 こうすることによって先程作成してネットワークを使用することができる。

また service にも network を指定しなければならないため記述。(実はここが抜けてて時間を無駄にした…)

この記述を別の docker-compose.yml に記述するとネットワークを共有することができる。

この場合他のコンテナにアクセスする際は host 名がコンテナ名になるため、予め container_name などで指定してあげると良いかも。

Grape + Grape Entity で作る API

Grape + Grape Entity で作る API

Grape をよく使うので基本的な使い方から開発方法などを記す。 あと思いの外 Grape Entity が便利なので小技などをまとめておきたい。

Grape とは

github.com

Rails で RESTful な API を開発するためのフレームワーク。 Rails5 から提供された API モードで普通の Controller で簡単に開発することは可能になったが、 Grape の場合 REST に特化した DSL のためコードが追いやすい。

また Grape Entity と組み合わせることによってエンドポイント特有の結果を提供することが用意になる。

個人的にはコードが簡潔に保てるところが一番の強みだと思っている。

導入方法

「Grape 導入」で調べるといっぱい出てくるので割愛したい。 個人的に気にっているフォルダ構成は以下だ。

├── api
│   ├── base
│   │   └── api.rb
│   └── v1
│       ├── entities
│       │   ├── group_entity.rb
│       │   ├── idol_entity.rb
│       │   └── song_entity.rb
│       ├── groups.rb
│       ├── idols.rb
│       ├── root.rb
│       └── songs.rb

まず Rails プロジェクトの app 配下に api ディレクトリを作る。 そこに base と v1 というディレクトリを作る。 v1 ディレクトリはいわゆる API のバージョニングのためのもの。

api/base/api.rb と routes.rb にはそれぞれ以下が記述されているものとする。

# api/base/api.rb
module Base
  class API < Grape::API
    mount V1::Root
  end
end

# routes.rb
mount Base::API => '/'

こうすることによって API へのリクエストはすべて base/api.rb に流れる。 base の方の記述を見ると v1 に受け流している。 こうすることによって API のバージョンニングを用意にしている(のだと思う)。

v1 配下は Grape を記述していくところで entity ディレクトリは Grape Entity を記述するところである。

それでは Grape の構文を見ていく。

シンタックス

Grape

module V1
  class Idols < Grape::API
    resources :idols do
    desc 'return all idol'
    get '/' do
          @idols = Idol.all
      present @idols, with: V1::Entities::IdolEntity
        end
     end

    desc 'Create an idol'
    params do
      requires :name, type: String
      requires :group_id, type: Integer
    end
    post do
      @idol = Idol.new(name: params[:name], group_id: params[:group_id])
      if @idol.save
        status 201
        present @idol, with: V1::Entities::IdolEntity
      else
        status 400
        present @idol.errors
    end

     route_param :id do
       desc 'returns an idol'
         get do
           @idol = Idol.find(params[:id])
           present @idol, with: V1::Entities::IdolEntity
         end
       end
     end
  end
end

見ただけでどこに何が書いてあるかがわかりやすいと思います。 基本構文は以下のような感じです。

resource :{操作を加えるリソース名(モデル名)do
  desc '説明'
  paras do
    # パラメータを記述
  end
  {HTTP のメソッド} do
    # 処理
  end
end

このように記述することによって以下の操作が可能。

- GET: /idols ... 一覧の取得
- GET: /idols/1 ... 1件取得
- POST: /idols ... 作成

リソースの中にリソースをネストさせることも可能。

resource :groups do
  ... 
  resource :idols do
     ...
  end
end

この場合 idols には /groups/idols という URI でアクセスすることができる。

Grape Entity

上の例にも出てきているが present @idol, with: V1::Entities::IdolEntity の IdolEntity がそれである。 このファイルは以下のように記されている。

module V1
  module Entities
    class IdolEntity < Grape::Entity
      expose :id
      expose :name
      expose :group, using: V1::Entities::GroupEntity
    end
  end
end

expose に指定したフィールドをオブジェクトに問い合わせてその値を返してくれる。 この場合以下のような JSON オブジェクトが返ってくる。

{
  id: 1,
  name: "test",
  group: {
    id: 1,
   name: "test"
  }
}

ここでネストをさせているが Idol モデル belongs_to で Group に紐づくとする。 その場合紐づく group をたどって指定した GroupEntity に当てはめてくれる。

またモデルクラスに定義されているメソッドを返すこともできる。 例えば以下のような例が挙げられる。

module V1
  module Entities
    class IdolEntity < Grape::Entity
      expose :id
      expose :name do |idol|
        idol.full_name
      end
      expose :group, using: V1::Entities::GroupEntity
    end
  end
end

Idol クラスに full_name というメソッドが定義されているとする。 上のように expose :name にブロックを渡してあげることにより full_name の値を name として返すことができる。

まとめ

Grape を使って開発すると Controller に書くのに比べて可読性の高いコードが書けることがわかった。

Active Storage で複数枚画像を削除する方法

ActiveStorage で画像を複数枚削除する方法

Rails 5.2 で Active Storage が gem 使わずとも添付ファイルが実装できるため使用している。
Rails ガイド を見ると1つのオブジェクトに対して複数の画像を添付することができる has_many_attached という機能がある。
これは非常に便利なのだが削除に関しては、has_one_attached の場合(オブジェクトに対して1つの添付ファイルがある場合)のみしか記されていなかったため、ここに残しておく。

添付ファイルが1つの場合の削除

ガイドを見ると以下のように記されている。

# 物理削除
@user.avatar.purge

複数添付ファイルを削除する

以下のように書くとできた。
改めて見ると直感的にかけそうでした…

まずは view 。 アルバムというモデルの更新画面があるとするとこんな感じです。

<%= form_with model: @album, local: true  do |form| %>
    <p>現在登録されている画像(削除するものはチェックしてください)</p>
    <% @album.images.each do |image| %>
      <%= form.check_box :image_ids, {:multiple => true}, image.id, false %>
      <%= image_tag image, :size=>"100x100" %> <br>
    <% end %>
  <% end %>
  <%= form.submit %>

これにより params[:album][:image_ids] に削除するファイルの ID が入る。

次に controller 側。 送られてきた ID のファイルを1つずつ削除しておくだけ。

params[:album][:image_ids].each do |image_id|
  image = @album.images.find(image_id)
  image.purge
end

参考

www.nicholasshirley.com

NativeScript-Vue でログが見たい

NativeScript-Vue で console.log の内容を見る方法

NativeScript を使って開発していて不便に思ったので記す。

外部 API にリクエストを送る際、レスポンスが返ってきているか普段の JS での開発では console.log などにはいて確認すると思われるが、NativeScript で同じことするとなぜか出力されない…

どこかログファイルなどに書きに言っているのかと思いきやそれも見つからず八方塞がりだったのだが、ある設定を付け足すと見れた。

Vue.config.silent = false

これをプロジェクト配下の main.js に書いたところ使っているターミナル上にログとして console.log の内容が表示されるようになった!

この silent という属性がデフォルトで true になっていると Vue が何も教えてくれなくなるらしい…

他にもいろんな設定が下記のページに記されてあったため、今後使っていこうと思う。

API — Vue.js

Javascript (Webpackプロジェクト) で .env

JS で環境変数を管理する

dotenv による環境変数の管理

先日環境変数周りで失敗をしてしまったため .env を使った環境変数の管理について学んでいく。
Rails プロジェクトだと dotenv という gem があって、それを使って .env ファイルに書いた変数の値を読み出すって方法がある。

dotenv | RubyGems.org | your community gem host

もちろん .env は .gitignore に書いてパブリックにさらされることを防ぐ必要がある。

Javascript でも同じようなやり方で .env で環境変数を管理できるようなので試してみる。

やり方

1 dotenv をインストール

$ npm install --save-dev dotenv

# yarn の方
$ yarn add dotenv

2 dotenv を記述

TEST_ACCESS_TOKEN=xxxxxxxxxxxxxx

3 webpack の plugins に設定

require('dotenv').config();

const dotenvPlugin = new webpack.DefinePlugin({
  'process.env': {
    'TEST_ACCESS_TOKEN': JSON.stringify(process.env.TEST_ACCESS_TOKEN)
  }
});


# config の中の plugins の配列の中に関数を入れる
plugins: [dotenvPlugin]

4 JS 側から呼び出す

console.log(process.env.TEST_ACCESS_TOKEN);

docker-compose run で bundle install を動かす

docker-compose run で bundle install

地味に忘れてしまうのでここで記録。
docker-compose.yml を以下のように記述するととりあえず動いた。

version: '3'
services:
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/research_app
      - bundle:/usr/local/bundle # bundleの設定
    ports:
      - "3000:3000"
    depends_on:
      - db
volumes:
  bundle:
    driver: local

ボリュームにグローバルで gem が追加される /usr/local/bundle を追加すればよいらしい

Native Script + Vue.js + Mapbox で地図アプリを作ってみた

Native Script + Vue.js + Mapbox で地図アプリ

Native Script とは

Javascript もしくは Angular もしくは Vue を使って Web アプリとAndroidiOSアプリを同時に作れる統合開発環境

↑らしいw
これなら Web 屋さんの自分でも触れると思い使ってみることにした。
今回作るアプリには地図を表示することがマストだったため Mapbox で作った地図を表示させることをゴールとして作っていく!

Mapbox とは

簡単にいうと Google map みたいな地図サービスのことをいう。
Mapbox でアカウントを作ると開発者用にダッシュボードが表示される。
そこで地図のレイアウトを独自に作ることができ、その地図をアプリケーションで使用することができる。
具体的な組み込み方は後述。

Native Script で Hello World

まずはローカルで開発するために専用の npm パッケージをインストール。

$ npm install -g nativescript

次にプロジェクトを作る。

$ vue init nativescript-vue/vue-cli-template mapbox-app

vue-cli と同じような感じですね。 一旦プロジェクト内に移動し、以下のコマンドらを実行するとアプリが立ち上がるはず。

$ cd mapbox-app

$ npm run watch:ios

iOSエミュレータの設定は以下を参考に行ったら、できた。

NativeScript Advanced Setup—macOS

Hello World は以上

Mapbox を組み込む

Mapbox を組み込むために以下の npm パッケージをインストール。

$ npm install nativescript-mapbox --save

これは Mapbox 社が用意したものらしい…すばらしい!
このあと以下の構文をプロジェクト配下の src/main.js に追加。

Vue.registerElement("Mapbox", () => require("nativescript-mapbox").MapboxView)

次に地図を表示させるコンポーネント src/compoents/Map.vue を作っていきます。 内容は以下のような感じです。

<template>
    <Page class="page">
        <ActionBar class="action-bar" title="Vue Mapbox Example"></ActionBar>
        <GridLayout>
            <Mapbox
                accessToken="{自分のアクセストークン}"
                mapStyle="traffic_day"
                latitude="52.3833160"
                longitude="4.8991780"
                hideCompass="true"
                zoomLevel="4"
                showUserLocation="false"
                disableZoom="false"
                disableRotation="false"
                disableScroll="false"
                disableTilt="false"
                @mapReady="onMapReady($event)">
            </Mapbox>
        </GridLayout>
    </Page>
</template>

<script>
    import * as utils from "utils/utils";
    export default {
        data () {
            return { };
        },
        methods: {
            onMapReady(args) {
                // 地図上に線を塗る
                args.map.addPolyline({
                    id: 1, // optional, can be used in 'removePolylines'
                    color: '#F00', // Set the color of the line (default black)
                    width: 7, // Set the width of the line (default 5)
                    opacity: 0.6, //Transparency / alpha, ranging 0-1. Default fully opaque (1).
                    points: [
                        {
                            'lat': 52.3833160, // mandatory
                            'lng': 4.8991780 // mandatory
                        },
                        {
                            'lat': 52.3834160,
                            'lng': 10.8991880
                        },
                        {
                            'lat': 30.3835160,
                            'lng': 10.8991980
                        }
                    ]
                });
            }
        }
    };
</script>

<style scoped></style>

Mapbox に別途アカウント登録をしてアクセストークンを得る必要がありますが、たったこれだけで地図コンポーネントが作れちゃう。

次にこれを src/main.js で include して画面に表示。

import Map from './components/Map';

Vue.registerElement("Mapbox", () => require("nativescript-mapbox").MapboxView)
new Vue({
  render: h => h(Map),
}).$start();

こんな感じです。もともと HelloWorld.vue が記述されていたところを Map.vue に置き換えただけ。

この状態で iOSエミュレータを起動させると以下のように表示される。

f:id:h-piiice16:20180825121410p:plain

センスのない線だが、これで完成!

参考

Include Feature-Rich Maps in a NativeScript-Vue App with Mapbox

ほとんどこれの日本語訳って感じです。