Rails5のAPIモードでAPIをつくってみる(2)

Rails5のAPIモードでAPIをつくってみる(1)のつづき

検索機能つけてみる。

アクションを作る。

# GET /items/search
def search
  @items = Item.search(item_params)
  render json: @items
end

下記の詳細は、ここに書いてあった。

# Only allow a trusted parameter "white list" through.
  def item_params
    params.require(:item).permit(:name, :price)
  end

ルーティング設定する。ルーティングの説明は、ここに超詳しく書いてある。便利だなあ。cakephpより分かりやすい。コントローラのフォルダわけとかもcakephpよりやりやすいと思った。あと、パス作成も簡単でいい。

Rails.application.routes.draw do
  get '/items/search', to: 'items#search'
  resources :items
end

モデルにsearch関数を書く。
modelクラスの中でのselfの使い方
Active Record クエリインターフェイス

def self.search(data)
  name = data[:name].nil? ? '' : data[:name]
  items = self.where('name like ?', '%' + name + '%')
  items = items.where('price = ?', data[:price]) if data[:price].present?
  return items
end

これでとりあえずできた。sqlの見方調べよう。development.logに出力されてた。

SELECT "items".* FROM "items" WHERE (name like '%app%') AND (price = '500')

1回のセレクト文になってるから大丈夫っぽい。

Rails5のAPIモードでAPIをつくってみる(1)

プロジェクト作成

$ rails new api1 --api

rails server立ち上げる

$ rails server

おーAPIモードになってる。
とりあえず、商品名と金額を登録・取得できる簡単なAPIをつくってみます。

商品テーブルのscaffoldとtableをつくる

$ rails generate scaffold Item name:string price:int
      invoke  active_record
      create    db/migrate/20170325093816_create_items.rb
      create    app/models/item.rb
      invoke    test_unit
      create      test/models/item_test.rb
      create      test/fixtures/items.yml
      invoke  resource_route
       route    resources :items
      invoke  scaffold_controller
      create    app/controllers/items_controller.rb
      invoke    test_unit
      create      test/controllers/items_controller_test.rb
$ rails db:migrate

エラーになった。intじゃだめっぽい。integerらしい。

$ rails destroy scaffold Item
      invoke  active_record
      remove    db/migrate/20170325093816_create_items.rb
      remove    app/models/item.rb
      invoke    test_unit
      remove      test/models/item_test.rb
      remove      test/fixtures/items.yml
      invoke  resource_route
       route    resources :items
      invoke  scaffold_controller
      remove    app/controllers/items_controller.rb
      invoke    test_unit
      remove      test/controllers/items_controller_test.rb
$ rails generate scaffold Item name:string price:integer
$ rails db:migrate
== 20170325094155 CreateItems: migrating ======================================
-- create_table(:items)
   -> 0.0045s
== 20170325094155 CreateItems: migrated (0.0058s) =============================

とりあえずできた。http://localhost:3000/itemsにアクセスすると商品一覧がみられる。

get /items 商品一覧取得
post /items 商品登録
get /items/1 商品詳細
put|patch /items/1 更新
delete /items/1 削除

postmanで登録してみる

url: (post)http://localhost:3000/items
body: item[name] => 納豆
      item[price] => 200

result

{
  "id": 1,
  "name": "納豆",
  "price": 200,
  "created_at": "2017-03-25T13:12:32.375Z",
  "updated_at": "2017-03-25T13:12:32.375Z"
}

クロスドメインアクセスOKにする場合、下記が必要。
APIファーストでバックエンドとフロントエンドを別々に開発する時にハマるクロスドメインアクセス
cyu/rack-cors

Gemfileに下記を追加。既に書いてあってコメントアウトされてるので、コメントを外す。

gem 'rack-cors'

bundle installする

$ bundle install

config/application.rbに下記のような感じで書く。

config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'
    resource '*', :headers => :any, :methods => [:get, :post, :options]
  end
end

ローカル環境だと、上記rack-corsの設定いらなかった。とりあえず、一覧、詳細、追加、修正、削除が全部できている。あとは、認証、認可、検索とか追加して本番環境で試したい。

参考:
Rails による API 専用アプリ

RailsでAPIつくる – 参考サイト一覧

Rails5になってから、APIモードというのができたらしく余計なものをそぎ落としたAPIに特化したプロジェクトを作成できるらしい。

Rails5のAPIモードを超速で試す
Ruby on Rails 5のAPIモードと非APIモードのファイル差分
Rails5とAPIモードについての解説
Rails5 apiモード + JSONAPI ResourcesでAPIサーバを作る
Rails5 API + devise でユーザーの認証と追加機能を実装した API を作成する
webpack + React + Rails5 APIモードで環境構築
Rails 5.0.0.beta2 APIモードについて調べてみた
Rails5 APIではじめるSPA開発

Vimeo APIをPHPで使ってみる

createmyapp

VimeoのAPIを使ってみます。ドキュメントは、https://developer.vimeo.com/です。twitterとかfacebookとかと同じでappをつくったら、キーとかシークレットとかもらえて、それ使ってOAtuh2で認証して、RESTAPI使います。

https://developer.vimeo.com/appsから新しいAPPを作成する。

PHPとかpythonとかandroid、iPhoneとか用にSDKもあります。シンプルです。javascript用SDKはないようですが、割と人気な使いやすいのがあります。利用用途によって修正は必要かなと思います。

PHPとかの公式SDK(Libraries)

javascriptのライブラリ的なやつ
websemantics/vimeo-upload

zipcloud – 郵便番号データのAPI

郵便番号検索機能をWEBサービスで提供してくれています。

http://zipcloud.ibsnet.co.jp/doc/api

登録などは一切不要みたいです。
getで、http://zipcloud.ibsnet.co.jp/api/search?zipcode=1540017のように郵便番号を与えると、jsonで返ってきます。

{
	"message": null,
	"results": [
		{
			"address1": "東京都",
			"address2": "世田谷区",
			"address3": "世田谷",
			"kana1": "トウキョウト",
			"kana2": "セタガヤク",
			"kana3": "セタガヤ",
			"prefcode": "13",
			"zipcode": "1540017"
		}
	],
	"status": 200
}

不正な郵便番号を与えるとエラーが返ってきます。

http://zipcloud.ibsnet.co.jp/api/search?zipcode=15400171

{
	"message": "パラメータ「郵便番号」の桁数が不正です。",
	"results": null,
	"status": 400
}

桁数が正しいが存在しない郵便番号を与えると、エラーではないがnullが返ってきます。

http://zipcloud.ibsnet.co.jp/api/search?zipcode=1540099

{
	"message": null,
	"results": null,
	"status": 200
}

また、郵便番号にハイフンがあってもきちんと対応してくれている。
http://zipcloud.ibsnet.co.jp/api/search?zipcode=154-0017

{
	"message": null,
	"results": [
		{
			"address1": "東京都",
			"address2": "世田谷区",
			"address3": "世田谷",
			"kana1": "トウキョウト",
			"kana2": "セタガヤク",
			"kana3": "セタガヤ",
			"prefcode": "13",
			"zipcode": "1540017"
		}
	],
	"status": 200
}

あら、でもAjaxでgetで取得しようとしたらエラーになる。クロスドメイン関係のエラーなので、Ajaxのアクセスできないのかもしれない。仕方ないのでPHPでつくってみる。

今fuelphp使っている。

/**
 * zip cloudで郵便番号から住所情報を取得する関数(Ajax用)
 */
public function get_address($zip_code = null){
	if(!$zip_code) return $this->response(null);

	$context = stream_context_create(array(
		'http' => array('ignore_errors' => true)
	));

	header("Content-type:text/html;charset=UTF-8");
	$address = file_get_contents('http://zipcloud.ibsnet.co.jp/api/search?zipcode=' . $zip_code, false, $context);
	$address_arr = json_decode($address, true);

	if($address_arr['status'] != 200 || empty($address_arr['results'])) return $this->response(null);

	//カナを全角にする
	for($i = 1; $i < 4; $i++){
		$address_arr['results'][0]['kana' . $i] = mb_convert_kana($address_arr['results'][0]['kana' . $i], 'KV', 'UTF-8');
	}

	//都道府県をコードに変換する
	$address_arr['results'][0]['address1'] = Model_Building::get_prefecture_code_from_name($address_arr['results'][0]['address1']);

	$this->response(json_encode($address_arr['results'][0]));
}

これにjavascriptでAjaxでアクセスする。

function zipcloud(base_url, zip_code, class_name){
    if(!base_url || !zip_code) return;
    if(!class_name) class_name = '';

    $.ajax({
        type: "GET",
        url: base_url + 'hoge/' +  zip_code,
        dataType: "json",
        success: function(address){
            address = JSON.parse(address);
            $('.zipcloud_pref_code' + class_name).val(address['address1']);
            $('.zipcloud_city' + class_name).val(address['address2']);
            $('.zipcloud_address' + class_name).val(address['address3']);
            $('.zipcloud_city_kana' + class_name).val(address['kana2']);
            $('.zipcloud_address_kana' + class_name).val(address['kana3']);
        }
    });
}

これに郵便番号が変わったらonchangeでアクセスする。

googleで祝日取得(php)

参考:Google Calendar API で日本の祝日データを取得

private function get_syukujitsu($first_date, $end_date){
	$holidays_url = sprintf(
		'http://74.125.235.142/calendar/feeds/%s/public/full-noattendees?start-min=%s&start-max=%s&max-results=%d&alt=json' ,
		'outid3el0qkcrsuf89fltf7a4qbacgt9@import.calendar.google.com' ,
		$first_date,    // 取得開始日
		$end_date,    // 取得終了日
		7                // 最大取得数
	);
	if($results=file_get_contents($holidays_url)) {
		$results = json_decode($results, true);
		$holidays = array();
		if(isset($results['feed']['entry'])){
			foreach($results['feed']['entry'] as $val) {
				$date = $val['gd$when'][0]['startTime']; // 日付を取得
				$title = $val['title']['$t']; // 何の日かを取得
				$holidays[$date] = $title; // 日付をキーに、祝日名を値に格納
			}
		}
		ksort($holidays); // 日付順にソート
	}
	return $holidays;
}

$holidaysの中身

スクリーンショット 2014-03-26 8.44.41

要修正ですが、これで取得できた。googleありがとう。

YouTube API

cakePHPのviewでYouTubeAPIを初めて使った。divのid=”videoDiv”内に動画が自動で再生される。閲覧中断していた動画は中断したところから再生される。閲覧中断ボタンと、閲覧完了ボタンに対応している。といったようなことをした。

<?php $this->start('script')?>
<script src="//www.google.com/jsapi" type="text/javascript"></script>
<script>
	var ytplayer = null;
	<?php if($watch):?>
		var seek = <?php echo h($watch['Watch']['seek'])?>;
	<?php else:?>
		var seek = 0;
	<?php endif;?>

	google.load("swfobject", "2.1");

	function _run() {
		// The video to load.
		var videoID = "<?php echo h($movie['Movie']['code'])?>";
		// Lets Flash from another domain call JavaScript
		var params = { allowScriptAccess: "always" };
		// The element id of the Flash embed
		var atts = { id: "ytPlayer" };
		// All of the magic handled by SWFObject (http://code.google.com/p/swfobject/)
		swfobject.embedSWF("http://www.youtube.com/v/" + videoID + "?enablejsapi=1&playerapiid=player1",
			"videoDiv", "560", "315", "9", null, null, params, atts);
	}

	google.setOnLoadCallback(_run);

	function onYouTubePlayerReady(playerId) {
		ytplayer = document.getElementById("ytPlayer");
		ytplayer.seekTo(seek, true);
		ytplayer.playVideo();
	}

	//動画閲覧の中断
	function stop(movie_id){
		if(ytplayer){
			var seek = ytplayer.getCurrentTime();
			if(seek > 0){
				location.href = '<?php echo $this->Html->url(array('controller' => 'movies', 'action' => 'stop'))?>' + '/' + movie_id + '/' + seek;
			}
		}
	}

	//動画閲覧の終了
	function finish(movie_id){
		location.href = '<?php echo $this->Html->url(array('controller' => 'movies', 'action' => 'finish'))?>' + '/' + movie_id;
	}
</script>
<?php $this->end()?>