【rails】authenticate_with_http_token を使ってapiの認証機能を実装する
はじめに
今回やりたかったことはアプリからのリクエストを受け付けるapiの開発。やりたいことと流れは以下のようなイメージ
・Emailとパスワードでログインを行う
・ログインに成功したらトークンと有効期限を発行してクライアントに返す
・クライアントは毎リクエストトークンをhttpヘッダーに入れてリクエストする
また、今回はrails5のapiモードを使用しました。
テーブル設計とか準備
・users
id | ユーザーid |
---|---|
メール | |
password_digest | パスワード |
token | トークン |
token_expire | トークン有効期限 |
password_digestはrailsのhas_secure_passwordというのを使う場合にこのカラムが必要なようなので追加。has_secure_passwordはパスワード認証の仕組みを簡単に実装できる仕組みみたい。
とりあえずモデルとテーブルを作成する
$ ./bin/rails g model User
migrationファイルが生成されるのでカラムを追記する
db/migrate/20171009182615_create_users.rb
class CreateUsers < ActiveRecord::Migration[5.1] def change create_table :users do |t| t.string :email t.string :password_digest t.string :token t.datetime :token_expire t.timestamps end end end
modelファイルも生成されるのでバリデーションを追加
app/models/user.rb
class User < ApplicationRecord VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, format: { with: VALID_EMAIL_REGEX } has_secure_password validates :password, presence: true, length: { minimum: 6 } end
has_secure_passwordを使うためにbcryptというのが必要なのでGemfileに追記
Gemfile
gem 'bcrypt', '~> 3.1.7'
ここまでできたら以下を実行
$ ./bin/bundle install $ ./bin/rails db:migrate
実装
あとはプログラムを実装していく。
config/routes.rb
Rails.application.routes.draw do post '/users/signup', to:'users#signup' post '/users/signin', to:'users#signin' end
app/controller/users_controller.rb
# coding: utf-8 class UsersController < ApplicationController ## authenticate_with_http_tokenを使うために include ActionController::HttpAuthentication::Token::ControllerMethods ## signinとsingup以外で認証処理を行う before_action :auth, except: [:signin, :signup] ## サインアップ def signup user = User.new(user_params) if user.save success else failed(user.errors.full_messages) end end ## サインイン def signin ## 認証に成功したらランダム文字列でトークンを生成して有効期限1日をセット user = User.find_by(email: params[:email]) if user && user.authenticate(params[:password]) user.token = create_token user.token_expire = DateTime.now + 1 if user.save!(:validate => false) success({ token: user.token }) else error end else error('email または passwordが正しくありません') end end ## レスポンス返却用の簡易メソッド def success(data = nil) render json:{ status: 'success', result: data } end def error(messages = nil) render json:{ status: 'failed', messages: messages } end ## 以下 authenticate_with_http_token を使った処理 def auth ## 認証に失敗したらエラーレスポンスを返す authenticate_token || render_unauthorized end ## httpヘッダーに入っているtokenでdbを検索して有効期限のチェック ## authenticate_with_http_tokenの書き方でこのように書くみたい def authenticate_token authenticate_with_http_token do |token, options| user = User.find_by(token: token) user && DateTime.now <= user.token_expire end end def render_unauthorized render json:{ result:'failed' } end ## ランダム文字列を作る def create_token return ((0..9).to_a + ("a".."z").to_a + ("A".."Z").to_a).sample(50).join end def user_params params.permit(:email, :password) end end
確認はこんな感じで
$ curl -H 'Authorization: Token xxxxxxxxxxxxxxxxxxxxxxxxxx' http://localhost:3000/users
今回はトークンをDBに入れているけど、少し規模の大きいサービスだったらredisとかにもたせるのがよいのかな。
以上です。
rails console で group by の結果を確認
やりかったことはemailが重複しているレコードの件数をしらべたかっただけ。
実行されているSQLが期待通りだったのでこれでいいのかと思ってたけど、全然期待した結果にならない。。
$ User.select('email,count(*)').group('email') > User.select('email,count(*)').group('email') User Load (0.3ms) SELECT email,count(*) FROM "users" GROUP BY "users"."email" [#<User:0x007fc90f100ce0 id: nil, email: "test@test.com">, #<User:0x007fc90f0f80b8 id: nil, email: "test2@test.com">, ・・・
結果、こうするらしい
$ User.group(:email).count > User.group(:email).count (0.3ms) SELECT COUNT(*) AS count_all, "users"."email" AS users_email FROM "users" GROUP BY "users"."email" {"test@test.com"=>1, "test2@test.com"=>1, ・・・
慣れかな。以上です
【ionic2】ionicPageで各画面をurlに対応させる
はじめに
ionic1のルーティングまわりはui-routerで検索すれば色々出て来たけどionic2で何で検索すればよいのかまだよくわかってないけどとりあえずやり方はわかったのでメモしておく。デバッグのときとかはブラウザでリロードしたいし。あれ、けど目的はそれくらい。。?
とりあえずHome → Page1 → Page2 みたいな感じで画面遷移できるように。
それぞれ /#/home → /#/page1 → /#/page2 とURLもつようしてみる。
app/app.component.ts
・・・ import { HomePage } from '../pages/home/home'; @Component({ templateUrl: 'app.html' }) export class MyApp { //rootPage:any = HomePage; rootPage:any = 'home'; // ★ ココをあとでionicPageで指定する名前に変更 ・・・ }
app/app.module.ts
・・・ @NgModule({ declarations: [ MyApp //HomePage ], ・・・ entryComponents: [ MyApp //HomePage ], ・・・
以降、各ページ
pages/home/home.module.ts
import { NgModule } from '@angular/core'; import { IonicPageModule } from 'ionic-angular'; import { HomePage } from '../home/home'; @NgModule({ declarations: [ HomePage ], imports: [ IonicPageModule.forChild(HomePage) ], entryComponents: [ HomePage ] }) export class HomePageModule {}
pages/home/home.ts
import { Component } from '@angular/core'; import { NavController, IonicPage } from 'ionic-angular'; import { Page1Page } from '../page1/page1'; @IonicPage({ name: 'home' }) @Component({ selector: 'page-home', templateUrl: 'home.html' }) export class HomePage { constructor(public navCtrl: NavController) { } goPage1() { this.navCtrl.push('page1'); } }
page1、page2も同じように作ればNavControllerにpushするとurlも書き換わることが確認できた。以上です。
【ionic2】画面遷移のアニメーションを変更する
やりたかったこととしては画面の遷移の際のアニメーションでandroidでもiosみたいに左から右にスライドするようにしたいとういこと。
本来はプラットフォームごとに奨励されているアニメーションを使うべきだとは思うのだがやっぱりiosをベースで考えている人だとandroidでもそのように実装してほしいというのはよくあるのかと。
アプリ全体で設定する場合
app/app.module.ts
import { BrowserModule } from '@angular/platform-browser'; import { ErrorHandler, NgModule } from '@angular/core'; ・・・ @NgModule({ declarations: [ MyApp, HomePage, SignupPage ], imports: [ BrowserModule, // ★ ここで設定 IonicModule.forRoot(MyApp, { pageTransition: 'ios-transition' }) ], ・・・ }) export class AppModule {}
画面ごとに設定したい場合
各pagesのコンポーネント
this.navCtrl.push(MyPage, null, { animation: 'ios-transition' });
app.module.ts で設定するやり方は他にも色々設定できるみたいなので覚えた方がよさそう。
http://ionicframework.com/docs/api/config/Config/
以上です
【ionic2】無限スクロールの使い方
なかなか手をだせにずにいたionic2を次の案件では使えるように準備中。とりあえずionic1でできてたことをionic2でもできるようになることが当面の目標。今回はapiから取得したjsonからリスト表示で、無限スクロールを実装してみる。
blankでプロジェクト作成
$ ionic start test blank
まずはhttpをつかえるように。
app/app.module.ts
import { BrowserModule } from '@angular/platform-browser'; ・・・ import { HttpModule } from '@angular/http'; ## ★ これを追加 @NgModule({ ・・・ imports: [ BrowserModule, IonicModule.forRoot(MyApp), HttpModule ## ★ これを追加 ], ・・・ }) export class AppModule {}
つづいてコントローラ?を実装
src/pages/home/home.ts
import { Component } from '@angular/core'; import { Http } from '@angular/http'; import 'rxjs/add/operator/map'; @Component({ selector: 'page-home', templateUrl: 'home.html' c}) export class HomePage { page = 1; items = []; completed = false; constructor(public http: Http) { this.getUsers().subscribe(data => { if (0 < data.users.length) { this.items = this.items.concat(data.users); this.page++; } else { this.completed = true; } }); } doInfinite(infiniteScroll) { this.getUsers().subscribe(data => { if (0 < data.users.length) { this.items = this.items.concat(data.users); this.page++; } else { this.completed = true; } infiniteScroll.complete(); }); } getUsers() { return this.http .get('http://localhost:3000/users?page=' + this.page) .map(res => res.json()); } }
さいごにテンプレートを実装
src/pages/home/home.html
・・・ <ion-content padding> <ion-list> <ion-item *ngFor="let item of items"> {{ item.name }} </ion-item> </ion-list> <ion-infinite-scroll (ionInfinite)="doInfinite($event)" *ngIf="completed"> <ion-infinite-scroll-content></ion-infinite-scroll-content> </ion-infinite-scroll> </ion-content>
typescriptとかrxjsとかまだ全然わかってないけどとりあえずこれでできた。以上です。
rails5のapiモードを試した
ローカルPCで動作させてみる
まずは rbenv を使って ruby環境でrailsをインストールするところまでやる。
http://kimagureneet.hatenablog.com/entry/2017/09/27/143151
基本的にはここに書いた通りだが
・今回はrails5を使いたいのでバージョンの固定はしない
・apiモードを使いたいのでプロジェクト作成のコマンドに --api をつける
$ bundle exec rails new myapp --api
apiモードを使ってjsonを返す
とりあえずユーザの一覧を返すAPIを作ってみる。
routes に追加
Rails.application.routes.draw do get '/users', to: 'users#index' end
routes の確認
$ ./bin/rails routes Prefix Verb URI Pattern Controller#Action users GET /users(.:format) users#index
users コントローラの作成
$ ./bin/rails g controller users
controllers/users_controller.rb が作成されるのでjsonをかえすコードを追加
class UsersController < ApplicationController def index render json:{ users:[{id:1, name:'test1'},{id:2, name:'test2'}] } end end
ここまで作ったら http://localhost:3000/users にアクセスすると json が返されることを確認できた。
{"users":[{"id":1,"name":"test1"},{"id":2,"name":"test2"}]}
dbからデータを取得する
まずはdbを作成
$ ./bin/rails db:create
つづいて model の作成
$ ./bin/rails g model user
model を作成するとmigrationファイルも作成されるのでここにカラムを追加する。
db/migrate/20171009182615_create_users.rb
class CreateUsers < ActiveRecord::Migration[5.1] def change create_table :users do |t| t.string :email t.string :name t.timestamps end end end
migrate を実行
$ ./bin/rails db:migrate
ここまでにテーブルが作成されるのでコマンドラインからデータを登録して確認してみる
$ ./bin/rails console # 登録 > user = User.new > user.name = "test1" > user.email = "test1@test.com" > user.save # 確認 > User.all
データが登録されたことを確認できたので、APIとしてもusersデータをかえすように修正
contorollers/users_controller.rb
class UsersController < ApplicationController def index render json:{ users:User.all } end end
ページングに対応する
kaminari という gem が有名ぽいので使ってみる。
なにはともあれ kaminari をインストール
Gemfile
・・・ gem 'kaminari' ・・・
以下でインストールと設定を行う
# インストール $ bundle install $ vi config/initializers/kaminari_config.rb Kaminari.configure do |config| config.default_per_page = 2 ## デフォルト表示数を定義 ・・・
controllers/users_controller.rb
class UsersController < ApplicationController def index users = User.all.page(params[:page]) total = User.all.page.total_pages render json:{ total:total, users:users } end end
{"total":3, "users":[{"id":1,"name":"test1"},{"id":2,"name":"test2"}]}
http://localhost:3000/users?page=2
{"total":3, "users":[{"id":3,"name":"test3"}]}
とりあえずやりたいことはできた。以上です。
【angularjs】セレクトボックス(プルダウン)の使い方メモ
angularjs使うときはまたやりそうなのでメモしておく
まず、配列はどこかに一箇所に定義しておく
angular.module('myApp') .value('define', { 'area': { 1: '北海道', 2: '東北', 3: '関東', ・・・ }, });
テンプレートから参照できるようにコントローラで $scopeにいれる
angular.module('myApp') .controller('SampleCtrl', function(define) { $scope.areas = define.area; });
テンプレートで select box を作る
<select ng-model="user.area" ng-options="k as v for (k, v) in areas"></select>
これでもいける
<select ng-model="user.area"> <option value="">選択して下さい</option> <option ng-repeat="(k, v) in areas" value="{{k}}">{{v}}</option> </select>
以上です