rails updated_at の更新タイミング

r = Record.first
r.name // ""
r.updated_at = Mon, 05 Feb 2018 14:45:23 JST +09:00
r.update(name: "") // 同じ値で更新
r.updated_at // Mon, 05 Feb 2018 14:45:23 JST +09:00 updated_atは更新されない
r.update(name: "hello")
r.updated_at // Tue, 06 Feb 2018 12:28:11 JST +09:00 updated_atは値が更新されたので更新される

golang slack通知

メソッド作成

func sendSlack(ctx context.Context, msg string) error {
    const endPoint = "{ 通知先URL }"
    body := []byte(fmt.Sprintf(`{"text":"%s"}`, msg))
    req, err := http.NewRequest("POST", endPoint, bytes.NewBuffer([]byte(body)))
    if err != nil {
        return err
    }
    req.Header.Set("Content-Type", "application/json; charset=UTF-8")
    client := urlfetch.Client(ctx)
    resp, err := client.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    return nil
}

通知

sendSlack(ctx, "<!channel> エラー発生!")

選択時にclassを付与

angular-app/src/app/heroes/heroes.component.html

hero === selectedHero の条件を満たす時、selected クラスを付与する

<h2>My Heroes</h2>
<ul class="heroes">
  <li *ngFor="let hero of heroes" (click)="onSelect(hero)" [class.selected]="hero === selectedHero">
    <span class="badge">{{hero.id}}</span> {{hero.name}}
  </li>
</ul>

angular-app/src/app/heroes/heroes.component.ts

import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HEROES } from '../mock-heroes';

@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {
  hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };
  selectedHero: Hero;
  heroes: Hero[] = HEROES;
  myBool: boolean = true;

  constructor() { }

  ngOnInit() {
  }

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
    console.log(this.selectedHero.name);
  }
}

Angular で始めるモダン Web 開発セミナー

概要

angularとは?

spaを作るためのフレームワーク
* spa
* 1つのページで完結する
* jsでdom操作を行いコンテンツ切り替え
* メリット
* サーバーとクライアントを切り離せる

モジュール

複数のモジュールでアプリケーションを作れる。1つのモジュールでアプリケーションを作ってしまうことも多い。

コンポーネント

  • 1つのページを複数のコンポーネントで構成していく(ナビゲーションとコンテンツなど)
  • コンポーネントの中に別のコンポーネントを読み込むことができる

アプリ構成

e2e

テストに関するファイルが入ってるいる

src

アプリに関するソースが入っている
* index.html
app-root を読み込む旨記載してある
* main.ts
app.moduleを読み込む旨記載してある
* app module
app.componentを読み込む旨記載してある
* app.component

アプリ作成

パッケージインストール

npm install igniteui-js-blocks@5.0.1 --save
npm install hammerjs --save
npm install @types/hammerjs --save

参考

https://connpass.com/event/74820/

asyncパイプ Observable経由で渡された値を取得する(ローディング画面を表示)

API

backend/app.yaml

application: angular-gae-go-test
version: 1
runtime: go
api_version: go1.8

handlers:
- url: /apis/(.*)
  script: _go_app
- url: /(.*\.(gif|png|jpeg|jpg|css|js|ico))$
  static_files: dist/\1
  upload: dist/(.*)
- url: /(.*)
  static_files: dist/index.html
  upload: dist/index.html

backend/app.go

package main

import (
    "encoding/json"
    "net/http"
    "time"
)

func init() {
    http.HandleFunc("/apis/user", func(w http.ResponseWriter, r *http.Request) {
        u := User{Name: r.FormValue("name")}
        res, err := json.Marshal(u)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        w.Header().Set("Content-Type", "application/json")
        time.Sleep(3 * time.Second) // APIレスポンスまで3秒間待たせる
        w.Write(res)
    })
}

// User ...
type User struct {
    Name string `json:"name"`
}

angular app

angular-app/src/app/app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule }      from '@angular/core';
import { FormsModule }   from '@angular/forms';
import { HttpModule} from '@angular/http';

import { AppComponent } from './app.component';
import { UserService }  from './user.service'; // サービス登録

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpModule,
    FormsModule
  ],
  providers: [ UserService ], // サービス登録
  bootstrap: [AppComponent]
})
export class AppModule { }

angular-app/src/app/app.component.ts

import { Component } from '@angular/core';
import { Http, URLSearchParams, Headers }  from '@angular/http';
import { UserService } from './user.service';
import { Observable } from 'rxjs/Observable'
import { User } from './user';

@Component({
  selector: 'app-root',
  template: `
    <form>
      <label for="name">名前:</label>
      <input id="name" name="name" type="text" [(ngModel)]="name" />
      <input type="button" (click)="onclick()" value="送信" />
    </form>
    <!-- asyncを利用 -->
    <!--<div>{{(result | async)?.name}}</div>-->
    <div *ngIf="result | async as user; else prog">{{user.name}}</div>
    <ng-template #prog>Now Loading</ng-template>
  `,
})
export class AppComponent {
  name = '';
  // result = '';
  result: Observable<User> =  Observable.create(
    observer => {
      const user: User = {
        name: '',
      };
      observer.next(user);
      observer.complete();
    }
  );

  constructor(private userService: UserService) { } // userServiceの注入

  onclick() {
    this.result = this.userService.getUser(this.name) // subscribeは使わず、Observableオブジェクトをそのまま渡す
  }
}

angular-app/src/app/user.service.ts

import { Injectable } from '@angular/core';
import { User } from './user';
import { Observable } from 'rxjs/Observable'
import { Http, URLSearchParams, Headers }  from '@angular/http';
import 'rxjs/add/operator/map'

@Injectable() // Ingectableデコレーターを付与するとコンポーネントに対してサービスを引き渡せる
export class UserService {
  constructor(private http: Http) { }

  getUser(name: string): Observable<User>{
    let ps = new URLSearchParams();
    ps.set('name', name);
    return this.http.get('apis/user', {
      params: ps
    }).map(res=>res.json() as User);
  }
}

angular-app/src/app/user.ts

export class User {
  name: string;
}

jsonp(P4907)

サービスを使わない

app.module.ts

import { NgModule }      from '@angular/core';
import { FormsModule }   from '@angular/forms';
import { JsonpModule }  from "@angular/http";
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent }  from './app.component';

@NgModule({
  imports:      [ BrowserModule, FormsModule, JsonpModule ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

app.component.ts

import { Component } from '@angular/core';
import { Jsonp, URLSearchParams }  from '@angular/http';

@Component({
  selector: 'app-root',
  template: `
  <form>
    <label for="url">URL:</label>
    <input id="url" name="url" type="url" size="50" [(ngModel)]="url" />
    <input type="button" (click)="onclick()" value="検索" />
  </form>
  <div>{{count}}件</div>
  <ul>
    <li *ngFor="let comment of comments">{{comment}}</li>
  </ul>
  `,
})
export class AppComponent {
  url = 'http://gihyo.jp/';
  count = 0;
  comments: string[] = [];

  constructor(private jsonp:Jsonp) {}

  onclick() {
    let params = new URLSearchParams();
    params.set('url', this.url);
    params.set('callback', 'JSONP_CALLBACK');

    this.jsonp.get('http://b.hatena.ne.jp/entry/jsonlite/', { search: params })
      .subscribe(
        response => {
          let data = response.json() || {};
          this.count = data.count;

          let result: string[] = [];
          data.bookmarks.forEach(function(value: any) {
            if (value.comment !== '') {
              result.push(value.comment)
            }
          });
          this.comments = result;
        },
        error => {
          this.count = 0;
          this.comments = [];
          console.log('はてなサービスのアクセスに失敗しました。');
        }
      );
  }
}

サービスに切り出す

app.module.ts

特に切り出す際に修正の必要なし

import { NgModule }      from '@angular/core';
import { FormsModule }   from '@angular/forms';
import { JsonpModule }  from "@angular/http";
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent }  from './app.component';

@NgModule({
  imports:      [ BrowserModule, FormsModule, JsonpModule ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

app.component.ts

import { Component } from '@angular/core';
import { Jsonp, URLSearchParams }  from '@angular/http';
import { HatenaService } from './hatena.service'; // サービス読み込み

@Component({
  selector: 'my-app',
  providers: [ HatenaService ], // プロバイダ登録
  template: `
  <form>
    <label for="url">URL:</label>
    <input id="url" name="url" type="url" size="50" [(ngModel)]="url" />
    <button (click)="onclick()">検索</button>
  </form>
  <div>{{count}}件</div>
  <ul>
    <li *ngFor="let comment of comments">{{comment}}</li>
  </ul>
  `,
})
export class AppComponent {
  url = 'http://gihyo.jp/';
  count = 0;
  comments: string[] = [];

  constructor(private hatena:HatenaService) {} // サービスを注入

  onclick() {
    this.hatena.requestGet(this.url)
      .subscribe(
        data => {
          let result: string[] = [];
          data.bookmarks.forEach(function(value: any) {
            if (value.comment !== '') {
              result.push(value.comment)
            }
          });
          this.comments = result;
          this.count = data.count;
        },
        error => {
          this.count = 0;
          this.comments = [];
          console.log('はてなサービスのアクセスに失敗しました。');
        }
      );
  }
}

hatena.service.ts

import { Injectable } from '@angular/core';
import { Jsonp, URLSearchParams } from '@angular/http';
import { Observable } from 'rxjs/Observable';

import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';

@Injectable()
export class HatenaService {

  constructor(private jsonp: Jsonp) { }

  requestGet(url: string): Observable<any> {
    let params = new URLSearchParams();
    params.set('url', url);
    params.set('callback', 'JSONP_CALLBACK');

    return this.jsonp.get('http://b.hatena.ne.jp/entry/jsonlite/', { search: params })
      .map(
        response => {
          return response.json() || {};
        }
      )
      .catch(
        error => {
          return Observable.throw(error.statusText);
        }
      );
  }
}

非同期通信の実行 Httpサービス(P4762)

サービスを使わない場合

API

backend/app.go

package main

import (
    "encoding/json"
    "net/http"
)

func init() {
    http.HandleFunc("/apis/user", func(w http.ResponseWriter, r *http.Request) {
        u := User{Name: r.FormValue("name")}
        res, err := json.Marshal(u)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        w.Header().Set("Content-Type", "application/json")
        w.Write(res)
    })
}

type User struct {
    Name string `json:"name"`
}

app.yaml

application: angular-gae-go-test
version: 1
runtime: go
api_version: go1.8

handlers:
- url: /apis/(.*)
  script: _go_app
- url: /(.*\.(gif|png|jpeg|jpg|css|js|ico))$
  static_files: dist/\1
  upload: dist/(.*)
- url: /(.*)
  static_files: dist/index.html
  upload: dist/index.html

angular app

sample-app/src/app/app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule }      from '@angular/core';
import { FormsModule }   from '@angular/forms';
import { HttpModule} from '@angular/http';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpModule,
    FormsModule
  ],
  providers: [ ],
  bootstrap: [AppComponent]
})
export class AppModule { }

sample-app/src/app/app.component.ts

import { Component } from '@angular/core';
import { Http, URLSearchParams, Headers }  from '@angular/http';

@Component({
  selector: 'app-root',
  template: `
    <form>
      <label for="name">名前:</label>
      <input id="name" name="name" type="text" [(ngModel)]="name" />
      <input type="button" (click)="onclick()" value="送信" />
    </form>
    <div>{{result}}</div>
  `,
})
export class AppComponent {
  name = '';
  result = '';

  constructor(private http: Http) { }

  onclick() {
    // this.http.get('apis/user', {
    //   params: { name: this.name }

    //  URLSearchParamsで渡す場合
    let ps = new URLSearchParams();
    ps.set('name', this.name);
    this.http.get('apis/user', {
      params: ps

    /*
    // POST による通信の場合
    let ps = new URLSearchParams();
    ps.set('name', this.name);
    this.http.post('app/http.php', ps, {
      headers: new Headers({
        'Content-Type': 'application/x-www-form-urlencoded'
    })
    */

    /*
    //JSON形式で送信
    this.http.post('app/http.php', { name: this.name }, {
      headers: new Headers({ 'Content-Type': 'application/json' })
    */

      })
      .subscribe(
        response => {
          this.result = response.json().name;
        },
        error => {
          this.result = `通信失敗:${error.statusText}`;
        }
      );
  }
}

user.ts

export class User {
  name: string;
}

サービスに切り出す場合

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule }      from '@angular/core';
import { FormsModule }   from '@angular/forms';
import { HttpModule} from '@angular/http';

import { AppComponent } from './app.component';
import { UserService }  from './user.service'; // サービス登録

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpModule,
    FormsModule
  ],
  providers: [ UserService ], // サービス登録
  bootstrap: [AppComponent]
})
export class AppModule { }

user.service.ts

import { Injectable } from '@angular/core';
import { User } from './user';
// import { HttpModule} from '@angular/http';
import { Observable } from 'rxjs/Observable'
import { Http, URLSearchParams, Headers }  from '@angular/http';
import 'rxjs/add/operator/map'

@Injectable() // Ingectableデコレーターを付与するとコンポーネントに対してサービスを引き渡せる
export class UserService {
  constructor(private http: Http) { }
  getUser(name: string): Observable<User>{ // 戻り値はUserモデル
    let ps = new URLSearchParams();
    ps.set('name', name);
    return this.http.get('apis/user', {
      params: ps
    }).map(res=>res.json() as User);
  }
}

sample-app/src/app/app.component.ts

import { Component } from '@angular/core';
import { Http, URLSearchParams, Headers }  from '@angular/http';
import { UserService } from './user.service';

@Component({
  selector: 'app-root',
  template: `
    <form>
      <label for="name">名前:</label>
      <input id="name" name="name" type="text" [(ngModel)]="name" />
      <input type="button" (click)="onclick()" value="送信" />
    </form>
    <div>{{result}}</div>
  `,
})
export class AppComponent {
  name = '';
  result = '';

  constructor(private userService: UserService) { } // userServiceの注入

  onclick() {
    this.userService.getUser(this.name).subscribe(
      response => {
        this.result = response.name + "!"; // responseはuserモデル
      },
      error => {
        this.result = `通信失敗:${error.statusText}`;
      }
    );
  }
}

参考

https://qiita.com/lacolaco/items/364c5923f77458c468ac