Madogiwa Blog

主に技術系の学習メモに使っていきます。

RubyonRails:deviseのControllerをちょっとだけカスタマイズする

Moook(https://moook.herokuapp.com/)では、deviseで認証機能を作っています。
管理用にユーザーが新規登録されたらslack通知が行われるようにしたかったのですが、deviseはdefaultではコントローラーの実体のソースが無いので、一定の手順を踏む必要があります。その手順を備忘目的でメモしておきますφ(..)

やりかた

前提情報

今回手順を実施した環境の情報は下記の通りです。

gem version
rails 5.1.2
devise 4.3.0

また既にrails g devise Userを実施しており、devise用のユーザーモデルが作成済みの状態を想定しています。

コントローラーのソースファイルを生成する

rails g devise:controllers usersを実行し、コントローラーのファイルを作成します。実行するとapp/controller/users配下にdeviseの各種コントローラーが生成されます。

# deviseのコントローラーのファイルを生成
$ rails g devise:controllers users
Running via Spring preloader in process 46167
      create  app/controllers/users/confirmations_controller.rb
      create  app/controllers/users/passwords_controller.rb
      create  app/controllers/users/registrations_controller.rb
      create  app/controllers/users/sessions_controller.rb
      create  app/controllers/users/unlocks_controller.rb
      create  app/controllers/users/omniauth_callbacks_controller.rb

routeの変更

ルーティングの設定を生成したファイルを参照するように変更します。

routes.rb

  devise_for :users, :controllers => {
    :registrations => 'users/registrations'
   }

カスタマイズを加えてみる

カスタマイズを加える前に実際に生成されたファイルを見てみます。
Users::RegistrationsController < Devise::RegistrationsControllerと記載のあるとおり、元のdeviseのユーザー作成のコントローラーを継承したファイルが生成されていると見て取れます。
そのため、各メソッドのコメントアウトを解除(メソッドをオーバーライド)し、super以降に追加したい処理を記載すれば、処理を追加出来ます。

superの意味やrubyのクラスの継承については下記がわかりやすいと思います。 www.rubylife.jp

controllers/users/registrations_controller.rb

class Users::RegistrationsController < Devise::RegistrationsController
  # before_action :configure_sign_up_params, only: [:create]
  # before_action :configure_account_update_params, only: [:update]

  # GET /resource/sign_up
  # def new
  #   super
  # end

# 省略

end

今回は、ユーザー作成処理にカスタマイズを加え、Slack通知を行うようにしたかったので下記のようにしました。

controllers/users/registrations_controller.rb

  def create
    super
    notice_slack("Moookに新しいユーザーが登録されました!")
  end

結果

無事に通知を送ることが出来ましたヽ(´エ`)ノ

f:id:madogiwa0124:20171126221229p:plain

ちなみにSlack通知のやりかたについては下記に記載してます。

madogiwa0124.hatenablog.com

参考

qiita.com


RubyonRails:Slack通知処理を実装する

Moook(https://moook.herokuapp.com/)を運用していて、毎日管理画面を確認するのがめんどくさかったので、新規ページが追加された時にSlackに通知が送られるようにしましたφ(..)

その手順をメモしておきます。

やりかた

gemのインストール

今回は、slack-notifierというgemを使って実装していきます。

github.com

まずは、Gemfileに下記を追記して、bundle installを行います。

gem 'slack-notifier'

Slackの設定

slack-notifierではIncoming WebHooksWeb hook URLを使用して通知を送信します。
通知を送りたいワークスペースのチャネルの歯車マークからAdd an Appをクリックします。

f:id:madogiwa0124:20171126212742p:plain

その後、遷移先の画面にてIncoming WebHooksを検索し、Add Configrarionをクリックします。

f:id:madogiwa0124:20171126210215p:plain

f:id:madogiwa0124:20171126210343p:plain

そしてWebhook URLを取得します。

f:id:madogiwa0124:20171126210601p:plain

ちなみにこの画面では、通知を送る名前やアイコンも設定できます。

項目 内容
Customize Name 通知欄で表示される名前を設定出来ます
Customize Icon 通知欄で表示されるアイコンを設定できます

Moookではこんな感じにしました(・_・)

f:id:madogiwa0124:20171126210850p:plain

通知処理の実装

実装内容はすごく簡単です。

  1. Web hook URLを引数にインスタンスを生成
  2. 本文を引数に通知送信処理を実行

サンプル

def notice_slack
  # 先程取得したWebhook URLを引数にインスタンスを生成
  notifier = Slack::Notifier.new(webhook_url)
  # 通知を送信
  notifier.ping("test")
end

Moookの実際の実装はこんな感じです。slackの通知処理は他でも使いそうだったのでapplication_controller内に記述しました。また、Web hook URLは.envを使って環境変数化しました。

application_controller.rb

  # slack通知用メソッド
  def notice_slack(message)
    notifier = Slack::Notifier.new(ENV['SLACK_WEBHOOK_URL'])
    notifier.ping(message)
  end

page_controller.rb

  def create
    @page = Page.new(page_params)
    @page.user_id = current_user.id
    @page.html = @page.get_html(@page.url)
    if @page.save
      redirect_to @page, notice: '新しいページを登録しました。'
      # ページ追加時に通知を行う
      notice_page_info(@page)
    else
      render :new
    end
  end

  def notice_page_info(page)
    # 本文を生成
    message = <<~"EOS"
      Moookに新規ページが登録されました!
      名前:#{page.name}
      URL:#{page.url}
    EOS
    # Slack通知処理を呼び出し
    notice_slack(message)
  end

実行例

こんな感じでSlackに通知を送ることが出来ましたヽ(´エ`)ノ

f:id:madogiwa0124:20171126211817p:plain

参考

qiita.com

Ruby:Rubyでenum(列挙型)を使う※おまけ:全ての定数の値を取得する方法

下記ツールRubyenum(列挙型)を使いたかったけど、Rubyには明確な列挙型のようなクラスが無く、少しハマったのでφ(..)メモメモ

github.com

Rubyenum(列挙型)を使うには

Rubyにはenumがありません。そのためmoduleを使ってenumを表現します。
下記のように複数の定数値をmoduleの中に定義することで、enumのような振る舞いをさせることが出来ます!

# 定数のため、変更が入らないように`.freeze`を記載しています。
module Lang
  ENG = 'en'.freeze
  JPN = 'ja'.freeze
end

定数値を呼び出すにはモジュール名::定数名というような形で記述します。

puts Lang::ENG
# 結果 : en
puts Lang::JPN
# 結果 : ja

おまけ:全ての定数の値を取得する

moduleクラスに全ての定数値の値を取得するようなメソッドが無さそうだったので、自分で作ってみました。 constantsで全ての定数値の変数名を取得し、それを使ってevalで値を取得し直して配列で返しています。
moduleで定義した定数のリストに属さない入力値のチェック等に使えるのではないかなと思いますφ(..)

module Lang
  ENG = 'en'.freeze
  JPN = 'ja'.freeze

  def self.all_lang
    self.constants.map{ |lang| eval("#{lang}") }
  end
end

↓動作確認はこちら

参考

https://qiita.com/yuch_i/items/fa823a5ee3d569859137qiita.com

以上

Ionic:スワイプでページ更新する方法のメモ

Ionicで下スワイプでページ更新を行う方法をメモφ(..)

↓実装イメージはこんな感じです。

f:id:madogiwa0124:20171112232529g:plain

手順

概要

Ionicでスワイプでページ更新を実装するには、ion-refresherを使用します。
Viewのion-content内にion-refresherを配置し、(ionRefresh)="doRefresh($event)"のように実行メソッドを定義します。

詳細は下記を参照頂ければ!※Ionicの公式ドキュメントです

Refresher - Ionic API Documentation - Ionic Framework

Viewへの配置

下記のようにView内のion-content配下にion-refresherを配置します。

<ion-content no-padding>
  <ion-refresher (ionRefresh)="doRefresh($event)">
    <ion-refresher-content></ion-refresher-content>
  </ion-refresher>
</ion-content>

更新処理の実行及び完了の実装

Viewへの配置が終わったら.ts側にページ更新及び完了の処理を実装していきます。
下記の実装では、API呼び出しによるデータ取得の完了または、3秒経過したら完了とし、ローディングを終了させています。

@IonicPage()
  
@Component({
  selector: 'page-page-index',
  templateUrl: 'page-index.html',
})
export class PageIndexPage {
  pages = [];
  result = '';
  api_url = 'https://moook.herokuapp.com/pages.json';
  refresher = null;

  constructor(public navCtrl: NavController, public navParams: NavParams, private http: Http, public loadingCtrl: LoadingController) {
    // ロード画面を表示
    this.presentLoading();
    // APIからデータを取得
    this.get_pages_data();
  }

  doRefresh(refresher) {
    this.refresher = refresher;
    // APIからデータを取得
    this.get_pages_data();
    // タイムアウトの設定
    setTimeout(() => {
      // ページ更新を完了とし、処理を中断
      refresher.complete();
    }, 3000);
  }

  get_pages_data(){
    this.http.get(this.api_url).subscribe(
      respons => {
        let result = respons.text();
        this.pages = [];
        JSON.parse(result || null).forEach(data => {
          this.pages.push(new Page(data)); 
        });
        if (this.refresher != null) {
          // データ取得が終わったら完了
          this.refresher.complete();
        }
      },
      error => {
        let result = 'faild : '+ error.statusText;
        console.log(result);
      }
    );
  }

}

完全なソースコードは下記に配置してありますφ(..)

github.com

以上です。

Ionic:読み込み中にローディングのポップアップを表示する

Ionicでローディングのポップアップを表示する手順をメモφ(..)

↓イメージはこんな感じです。
f:id:madogiwa0124:20171112215240g:plain

手順

Ionicでローディング画面を表示するにはLoadingControllerを使用します。
LoadingControllerの使い方の概要は下記の通りです。

methods memo
create ローディング画面を作成
present ローディング画面を表示
dismiss ローディング画面を非表示化

詳細は、下記を参照頂ければ!※Ionicの公式ドキュメントです。

↓Loadingコンポーネントの説明とデモ ionicframework.com

↓LoadingControllerのAPIドキュメント ionicframework.com

今回は、現在開発中のMoook.FrontendでAPIからデータを取得中にローディング画面を表示してみました。
下記がソースコードです。※必要部分だけ抜粋しています。

// ローディング用のモジュールをインポート
import { LoadingController } from 'ionic-angular';

@IonicPage()
  
@Component({
  selector: 'page-page-index',
  templateUrl: 'page-index.html',
})

export class PageIndexPage {
  pages = [];
  result = '';
  api_url = 'https://moook.herokuapp.com/pages.json';
  loader = null;
  // コンストラクタでLoadingControllerの変数を宣言
  constructor(public navCtrl: NavController, public navParams: NavParams, private http: Http, public loadingCtrl: LoadingController) {
    // ロード画面を表示
    this.presentLoading();
    // APIからデータを取得
    this.get_pages_data();
  }

  presentLoading() {
    this.loader = this.loadingCtrl.create({
      content: "Please wait...",  // 表示する文字列
      duration: 3000  // タイムアウト値(自動で消えるまでの時間)
    });
    // ローディング画面を表示
    this.loader.present();
  }

  get_pages_data(){
    this.http.get(this.api_url).subscribe(
      respons => {
        let result = respons.text();
        this.pages = [];
        JSON.parse(result || null).forEach(data => {
          this.pages.push(new Page(data)); 
        });
        if (this.loader != null) {
          // ローディング画面を非表示
          this.loader.dismiss();
        }
      },
      error => {
        let result = 'faild : '+ error.statusText;
        console.log(result);
      }
    );
  }
}

ソースコードの全体が知りたい人はGithubからどうぞヽ(´エ`)ノ

github.com

Ionic:API呼び出し処理の実装方法メモ

Moookのモバイル対応でAPIからデータを取得する対応をしたので、その方法を備忘目的でメモφ(..)

↓こんな感じでMoook(https://moook.herokuapp.com/pages)から取得したデータをIonic側で取得し、表示しています。

f:id:madogiwa0124:20171105115923g:plain

やりかた

モジュールのインポート

まずは、app.module.tshttpモジュールを追記し、API呼び出しに使用するモジュールをインポートします。

/* 省略 */
import { HttpModule } from '@angular/http'; // 追記

@NgModule({
  declarations: [
    MyApp,
    HelloIonicPage,
    ItemDetailsPage,
    ListPage,
    PageIndexPage,
    PageShowPage
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp),
    HttpModule // 追記
  ],
/* 省略 */
})
export class AppModule {}

API呼び出し処理の実装

モジュールをインポートしたらコンポーネント側でAPI呼び出し処理を実装します。

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { PageShowPage } from '../page-show/page-show';
import { Http } from '@angular/http';  // 追記

/**
 * Generated class for the PageIndexPage page.
 *
 * See https://ionicframework.com/docs/components/#navigation for more info on
 * Ionic pages and navigation.
 */

@IonicPage()
  
@Component({
  selector: 'page-page-index',
  templateUrl: 'page-index.html',
})
export class PageIndexPage {
  pages = [];
  result = '';
  api_url = 'https://moook.herokuapp.com/pages.json';

  constructor(public navCtrl: NavController, public navParams: NavParams, private http: Http) {
    this.get_pages_data();
  }

  get_pages_data(){
   // API呼び出し処理
    this.http.get(this.api_url).subscribe(
     // 成功時
      respons => {
        let result = respons.text();
        this.pages = JSON.parse(result || null);
      },
      // 失敗時
      error => {
        let result = 'faild : '+ error.statusText;
        console.log(result);
      }
    );
  }
}

まとめ

とりあえずIonicでのAPI呼び出し処理の実装をまとめてみましたφ(..)
まだまだ、APIのURLを共通化したりとか、HTTP呼び出し処理の共通化とか、まだまだ考慮しないといけない点はあると思いますが、これから色々勉強していこうと思います・・・!
(誰か教えてください。。。)

RubyonRails:deviseのcurrent_userの返り値をカスタマイズする

deviseのヘルパーメソッドcurrent_userの返り値がデフォルトだとnilになるが、 空のUserインスタンスを返したかったけどハマったので、やり方をメモφ(..)

やり方

application_controller.rbcurrent_userをoverrideする。
しかし、alias_method :devise_current_user, :current_userで、aliasを記載する必要があるため注意。

class ApplicationController < ActionController::Base
  alias_method :devise_current_user, :current_user
  def current_user
    if devise_current_user.nil?
      User.new
    else
      User.find_by_id(devise_current_user.id)
    end
  end
end

しかし

一旦上記手順でやろうとしたが、このやり方だとuser_signed_in?等が上手く動かない等の問題が発生してしまいました。。。
※current_userがnilかどうかでログイン状態を判定しているため、未ログイン時に空のUserインスタンスが設定されていると不正にログインされているように判定されてしまっているように思えた。。。

そのため、controllercurrent_userインスタンス変数に格納し、それをnull判定して空のインスタンスを設定してViewでもそれを参照するようにした。
DBアクセスも減るし、ライブラリとの依存も減る気がするので、なかなか良いんじゃないかと思いました。

class PagesController < ApplicationController
  before_action :set_user

  def index
    # ログイン有無により処理を分岐
    if !@user.id.nil?
      @pages = Page.favorited_pages(current_user).page(params[:page])
      @favorites = Favorite.find_by(user_id: current_user.id)
    else
      @pages = Page.all.order('updated_at DESC').page(params[:page])
    end
  end

  private

    def set_user
      # current_userがnilだったら空のUserインスタンスを設定
      @user = current_user || User.new
    end
end

参考

codenote.net