edy hub

プログラミングやライフスタイルについて書き綴っています

RailsでZendeskAPIを使ってチケット情報を取得する

準備

API TOKENの発行をしてください

ご自身の管理画面の /agent/admin/api/settings から発行可能です。

https://<your site>.zendesk.com/agent/admin/api/settings

手順

Gemの導入

gem "zendesk_api"

bundle install

configファイルを用意

class Zendesk
  def self.client
    @client ||= ZendeskAPI::Client.new do |config|
      # Mandatory:

      config.url = ENV['ZENDESK_URL']

      # Basic / Token Authentication
      config.username = ENV['ZENDESK_USERNAME']

      # authentication
      config.token = ENV['ZENDESK_TOKEN']

      # Optional:

      # Retry uses middleware to notify the user
      # when hitting the rate limit, sleep automatically,
      # then retry the request.
      config.retry = true

      # Raise error when hitting the rate limit.
      # This is ignored and always set to false when `retry` is enabled.
      # Disabled by default.
      # config.raise_error_when_rate_limited = false

      # Logger prints to STDERR by default, to e.g. print to stdout:
      require 'logger'
      config.logger = Logger.new(STDOUT)

      # Changes Faraday adapter
      # config.adapter = :patron

      # Merged with the default client options hash
      # config.client_options = {:ssl => {:verify => false}, :request => {:timeout => 30}}

      # When getting the error 'hostname does not match the server certificate'
      # use the API at https://yoursubdomain.zendesk.com/api/v2
    end
  end
end

環境変数の設定

.env で管理しています。

ZENDESK_URL=https://sample.zendesk.com/api/v2
ZENDESK_USERNAME=login.email@zendesk.com
ZENDESK_TOKEN=TOKEN

ZENDESK_TOKEN は冒頭で取得したトークンを割り当ててください。

コンソールで疏通確認

> client  = Zendesk.client
> client.tickets
# => チケット一覧を返します

FlutterのサンプルAppのコードを読んでみる

はじめに

Flutterのアプリのセットアップが完了したら、AndroidStudioからアプリケーションを新規作成します。

File > New > New Flutter Project...を選びます。

一番左にある「」を選択すると自動でmain.dartを伴ったサンプルアプリが誕生しました。

f:id:yuki-eda0629:20191202003124p:plain
New Flutter Project

元々のコード

こちらが自動生成されたコードになります。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      // This call to setState tells the Flutter framework that something has
      // changed in this State, which causes it to rerun the build method below
      // so that the display can reflect the updated values. If we changed
      // _counter without calling setState(), then the build method would not be
      // called again, and so nothing would appear to happen.
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          // Column is also a layout widget. It takes a list of children and
          // arranges them vertically. By default, it sizes itself to fit its
          // children horizontally, and tries to be as tall as its parent.
          //
          // Invoke "debug painting" (press "p" in the console, choose the
          // "Toggle Debug Paint" action from the Flutter Inspector in Android
          // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
          // to see the wireframe for each widget.
          //
          // Column has various properties to control how it sizes itself and
          // how it positions its children. Here we use mainAxisAlignment to
          // center the children vertically; the main axis here is the vertical
          // axis because Columns are vertical (the cross axis would be
          // horizontal).
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'ボタンを押した回数が記録されます',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

RunしてみるとAndroidバイスエミュレーターが立ち上がります。

f:id:yuki-eda0629:20191202003317p:plain
ボタン押下数をカウントするシンプルなアプリ

コードの読み解き

さてアプリケーションをさくさく作り進めていきたいところですが、Dartについては全くの未経験者なので一度立ち止まってコードの理解に勤しむことにします。

import

import 'package:flutter/material.dart';

冒頭にあるimport文は外部ライブラリを呼び出しを意味しています。

ライブラリファイルをインポートするときには、上記のようにファイルの URI を指定するために package: ディレクティブを使うことができます。

packages:パッケージ名/公開ライブラリファイル の形式でimportすると、外部ライブラリを読み込む事ができます。

メイン関数

void main() => runApp(MyApp());

voidは、「何も返さない」という意味で戻り値がない場合に用います。

main.dart ファイルを実行すると呼ばれるいわゆるエントリーポイントとなる関数が main 関数です。

アプリを起動するための処理を書くことになります。

MyAppクラス

次にクラスが登場します。

コメントアウトは英語を和訳して下部に日本語を書いていきます。

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  // このウィジェットはアプリケーションのルート(大元)です。
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        // これがあなたのアプリケーションのテーマとなります。
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        // "flutter run"というコマンドを実行してみてください。
        // すると青いツールバーが目に入るでしょう。
        // アプリケーションを停止せずに、下のprimarySwatch(メインカラーを司るプロパティ?)を
        // Colors.greenに変更してみてください。
        // そして"ホットリロード"を起動してください。
        // カウンターがゼロにならないことに気づくでしょう。
        // アプリケーションは再起動していないのです。
        
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

コメントアウトにあったようテーマカラーを変更してみます。

primarySwatch: Colors.green

見事全体のカラーが変更されました。

f:id:yuki-eda0629:20191202011546p:plain
primarySwatch: Colors.green

class MyApp extends StatelessWidget { ... }

次に、クラスに記載のあるextendsStatelessWidgetについてです。

MyAppクラスはStatelessWidgetextendsしています。

(これだけだとさっぱり分からない...)

簡単に説明すると、Flutterが用意しているWidgetには2種類あります。

それがStatelessWidgetStatefulWidgetです。

アプリの状態を表すStateを扱う場合はStatefulWidget、そうでない場合はStatelessWidgetを利用します。

StatelessWidgetの特徴

- StatelessWidgetを継承した1つのクラスで構成されている
- StatelessWidgetはbuildメソッドを持ち、Widgetもしくはテキストを返す
- 返すWidgetはStatefulWidget(MaterialAppとか)でもOK
- 「state(状態)」の概念がないため、動的に変化しないWidget

StatefulWidgetの特徴

- Widgetにstateの概念をいれて拡張したもの
- stateが変化したときに再描画を指示するメソッドが組み込まれている
- StateクラスとWidgetクラスの2つのクラスで構成されている
- StatefulWidgetはbuildメソッドを持たず、createStateメソッドを持ち、これがStateクラスを返す
- StatefulWidget自体はシンプルな構造で複雑な処理等はStateクラスで持つ
- Stateクラスで機能的なメソッドを実装する
- Stateクラスはbuildメソッドをもち、Widgetを返すときに定義したメソッドを
- アクションとして組み込める
- returnできるWIdgetはいくつも種類があり、TextやImage、TextInput、Scaffold等
- ScaffoldはルートページとなるデザインWidgetで基本的なレイアウト構造

参考:Flutterの基礎 - Qiita

ActiveRecordのenumで定義した要素を元にセレクトボックスを作成する

はじめに

ActiveRecordenumで定義した要素をセレクトボックスを作りたいときの用法用量です。 ※gemを追加しても同等のことができますがコード量も少ないので自作で進めます。

i18n(Internationalization)について

モデルのattributeのi18nは通常、'activerecord.attributes.#{model_name}.#{attribute}'に定義します。 例えば、Userモデルのnameに適用する場合には、config/locales/ja/activerecord/user.ymlのようなファイルを作成します。

ja:
  activerecord:
    models:
      user: ユーザー
    attributes:
      user:
        name: 名前

取得時は User.human_attribute_name(:name) という形でアクセスします。

enumを管理する場合

本題に移ります。

今回は例として、Userがgenderという属性を定義しているとします。 genderにはfemaleとmaleというを値を持たせます。 - female(女性) - male(男性)

class User < ApplicationRecord
  enum gender: {
    female: 0,
    male: 1
  }
end

この後にしがちな(想定しそうな)yamlの定義の仕方としては下記のような記述でしょうか。

ja:
  activerecord:
    models:
      user: ユーザー
    attributes:
      user:
        gender: 
          female: 女性
          male: 男性

このようにuesr > gender > attributeの順にネストして直接定義することは出来ません。

この場合、以下のような形でモデルと同じ階層に定義してあげる必要があります。(http://guides.rubyonrails.org/i18n.html)

ja:
  activerecord:
    models:
      user: ユーザー
    attributes:
      user:
        name: 名前
        gender: 性別
      user/gender:  # model/attribute
        female: 女性
        male: 男性

定義方法に注目してください。

これにより下記のようなアクセスが可能になります。

User.human_attribute_name('gender.female')
# => 女性

User.human_attribute_name('gender')
# => 性別
# こちらも問題なく呼べる

Modelに便利メソッドを定義

基底モデルにenum専用のメソッドを追加していきます。 変更するファイルはapp/models/application_record.rbです。

class ApplicationRecord < ActiveRecord::Base

  self.abstract_class = true

  def self.human_attribute_enum_value(attr_name, value)
    human_attribute_name("#{attr_name}.#{value}")
  end

  def human_attribute_enum(attr_name)
    class.human_attribute_enum_value(attr_name, [attr_name])
  end

end

これで、以下のようにアクセスすることが出来ます。

user = User.new(gender: :female)
user.human_attribute_enum(:gender)
#=> 女性

User.human_attribute_enum_value(:gender, :male)
#=> 男性

form用のメソッドも追加

最後にselectに値を渡していきます。 特にメソッドを使わなくともこのように書くことも出来ます。

app/views/users/_form.html.slim

# テンプレートはslimを使用
# app/views/users/_form.html.slimの呼び出し元でUserオブジェクトのインスタンスをローカル変数で定義してあります

= form_for user do |f|
  = f.select :gender, User.genders.map {|k, _| [User.human_attribute_enum_value(:gender, k), k] }.to_h #=> {"女性"=>"female","男性"=>"male"}

メソッドを追加して記述するとすると下記のようになります。

app/models/application_record.rb

class ApplicationRecord < ActiveRecord::Base

  # ...

  def self.enum_options_for_select(attr_name)
    send(attr_name.to_s.pluralize).map {|k, _| [human_attribute_enum_value(attr_name, k), k] }
  end

end

※ このように使います。

User.enum_options_for_select(:gender) #=> {"女性"=>"female","男性"=>"male"}

最後にformに追加します。

app/views/users/_form.html.slim

= form_for @user do |f|
  = f.select :gender, User.enum_options_for_select(:gender)

これで [["女性", "female"], ["男性", "male"]]の組み合わせのセレクトボックスを生成することができました!

【Vue.js】v-onディレクティブに引数を渡す

v-onディレクティブに引数を渡すのは非常に簡単です。

引数を渡す前

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Vue.js App</title>
    <link rel="href + exp" href="main.css">
</head>
<body>
<div id="app">
  <p>現在{{ number }}回クリックされています</p>
  <button v-on:click="countUp">カウントアップ</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="sample.js"></script>
</body>
</html>
new Vue({
  el: '#app',
  data: {
    number: 0,
    x: 0,
    y: 0
  },
  methods: {
    countUp: function() {
      this.number += 1
    }
  }
})

f:id:yuki-eda0629:20190922171912p:plain
画面表示

このような表示になっています。 ボタン押下時にカウント数が1ずつ増加していきます。

今回の変更

さて、ここでcountUpメソッドに引数を渡して、1クリックでのカウントを3倍にしてみましょう。

html側のcountUpに3という数値を渡します。

<div id="app">
  <p>現在{{ number }}回クリックされています</p>
  <button v-on:click="countUp(3)">カウントアップ</button>
</div>

続いて、jsはこのように変更します

new Vue({
  el: '#app',
  data: {
    number: 0,
    x: 0,
    y: 0
  },
  methods: {
    countUp: function(times) { // timesとして数値を受け取る
      this.number += 1 * times // timesを掛ける
    }
  }
})

これで、1クリックで3ずつインクリメントされるようになりました! 引数を渡すのも直感的でわかりやすいですね!

さらに発展(eventを取得する)

eventオブジェクトに起因するデータに対して、引数を元に何らかの処理を加えたいケースもありますよね。

その場合は、渡したい引数に加えて、$eventを引数に加えます。

渡す位置に決まりはなく、順不同です。

例えば、テキストにマウスカーソルを当てたときに、そのカーソルのブラウザ上の位置を取得してX座標、Y座標として表示したいとき。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Vue.js App</title>
    <link rel="href + exp" href="main.css">
</head>
<body>
<div id="app">
  <p v-on:mousemove="changeMousePosition">マウスを載せてください</p>
  <p>X: {{x}}, Y: {{y}}</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="sample.js"></script>
</body>
</html>
new Vue({
  el: '#app',
  data: {
    number: 0,
    x: 0,
    y: 0
  },
  methods: {
    changeMousePosition: function(event) {
      this.x = event.clientX;
      this.y = event.clientY;
    }
  }
})

f:id:yuki-eda0629:20190922173233p:plain
位置情報を取得

では、引数を渡してこの座標を半分にしてみましょう。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Vue.js App</title>
    <link rel="href + exp" href="main.css">
</head>
<body>
<div id="app">
  <p v-on:mousemove="changeMousePosition(2, $event)">マウスを載せてください</p>
  <p>X: {{x}}, Y: {{y}}</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="sample.js"></script>
</body>
</html>
new Vue({
  el: '#app',
  data: {
    number: 0,
    x: 0,
    y: 0
  },
  methods: {
    changeMousePosition: function(divideNum, event) {
      this.x = event.clientX / devideNum;
      this.y = event.clientY / devideNum;
    }
  }
})

htmlファイルでは、$eventという特別な変数を用いています。 これで、Vueで定義したメソッドにDOMイベントを渡すことができるようになります。

参考

jp.vuejs.org

awkコマンドを理解していく

awsとの接触

はじめてawkコマンドと出会ったときに、衝撃を受けた。
意味分からんと。 でもこれを書く先輩エンジニアに憧れも抱いた...

mail_users=(`awk 'F==0{a[$1]=$2;next}{print $1 "\t" a[$1] "\t" $2}' <(cat employee.csv | awk -F',' '{print $1" " $8}' | sed -e '1d' -e '$d') F=1 <(/Users/guest/.nodebrew/current/bin/node sample.js | sed -e 's/[^0-9]//g' | sed -e 's/0*\([0-9]*[0-9]$\)/\1/g' | sort -nu)`)

※一部ファイル名とか変えてます

ということで、awk芸人になっていきましょう。

序章

awkはテキストファイルの一行毎に処理を行うスクリプト言語

awk 'パターン {アクション}' ファイル名」で、テキストファイルを1行ずつ読み、パターンに合致した行に対して、アクションで指定された内容を実行する。

基本的なオプション

オプション 意味
-f ファイル名 awkスクリプトが書かれたファイルを指定する
-F 区切り文字 区切り文字を指定する(デフォルトは空白文字)
-v 変数名=値 変数を定義する

オプションである -F'[フィールド区切り文字]' を指定しない場合、 区切り文字には、タブまたは半角スペースが選択される。

awk '{print $2,$3}' sample.csv # 2,3列目

awk -F, '{print $2,$3}' sample.csv # ,で区切って 2,3列目を出力

参考

qiita.com

qiita.com

www.techscore.com

シェルスクリプトの基礎と便利コマンド集

変数

# イコールの両端にスペースを空けない
str="hello"

# こういう書き方ができる
echo $srt # =>hello
echo "$str" # =>hello
echo "${str}" # => hello

# 文字列の連結
# +は要らない
echo $str$str # hellohello
# スペースはそのまま反映される
echo "$str $str" # hello hello

egrep

  • grepの拡張版らしい
  • grepに'-E'オプションを付与して実行するのと同義

egrepとgrepの違い - Qoosky

正規表現もメモっておく

パターン 意味
^ 行頭
$ 行末
. 任意の一文字
[ABC] ABCのいずれか
[A-Z] A~Zの範囲いずれか
[^ABC] ABCいずれでもない
[^A-Z] A~Zの範囲いずれでもない
+ 1回以上の繰り返し
* 0回以上の繰り返し
{n} n回の繰り返し
{n}, n回以上の繰り返し
{n,m} n回以上m回以下の繰り返し
r1 r2 | 正規表現r1かr2のいずれか

参考

【シェル芸人への道】Bashの変数展開と真摯に向き合う - Qiita

シェルスクリプトのtrap

trapは シェルスクリプトの実行結果に関わらず、 trapコマンドを後処理として実行させることができるようになります。

trap 'コマンド' シグナルリスト

※実行中のシェルスクリプトに対して送出されたシグナルは、trap コマンドを使用することで捕捉することが可能です。

シグナルについては、こちらを参考に

shellscript.sunone.me

さらに終了時のシグナルを分けて指定することで、エラー時・正常終了時のパターンに応じた処理の分担もできるようになります! スクリプトが途中終了した場合があったとしても、飛ぶ鳥跡を濁さずということで、綺麗に保ってくれるのです。

参考

qiita.com