It’s now or never

IT系の技術ブログです。気になったこと、勉強したことを備忘録的にまとめて行きます。

【Rails4】GrapeをつかってAPIを作成する

Grapeとは

REST-likeなAPIを作成する為のフレームワークです。 詳しくは、Grape githubを参照してください。

今回は、サンプルアプリケーションを作成し、jsonを返すAPIを動かしてみます。

サンプルプロジェクトにGrapeをインストール

任意のRailsアプリケーションを作成します。

rails new grape_api --skip-bundle -T

Gemのインストール

作成したアプリケーションのGemfileを編集し、grapeのGemをインストールします。

■ Gemfile

gem 'grape'
bundle install
// ※ローカルにインストールする場合は、--path vendor/bundleをつける

ディレクトリを作成する

API経由でデータを参照するために任意のモデルを作成します。

# userテーブルを作成しDBへ反映
rails g scaffold user name:string
rake db:migrate

ディレクトリは、下記のような構成にします。

- app
  - apis
    - api
      -v1
       - users
      -vXX
# とりあえず、v1のディレクトリのみ作成
mkdir -p app/apis/api/v1

今回は、上記のファイル構成を元に、

  • http://[ホスト]/api/v1/users
  • http://[ホスト]/api/v1/users/[user_id]

という2つのAPIを作成します。

ファイルの自動インポートの設定を行なう

apisディレクトリ配下に置かれたファイルが自動的にRailsのファイルパスに追加されるようにapplication.rbを以下のように編集します。

■ config/application.rb

module GrapeApi
  class Application < Rails::Application
    # app/api配下の.rbファイルを読み込み対象にする
    config.paths.add File.join('app', 'apis'), glob: File.join('**', '*.rb')
    config.autoload_paths += Dir[Rails.root.join('app', 'apis', '*')]
  end
end

APIのパスをルーティングする

APIのルートパスを定義します。

■ config/routes.rb

Rails.application.routes.draw do
・・・
  # APIモジュールのBaseクラスを'/'Pathとして定義する
  mount API::Base => '/'
・・・
end

Baseクラスを作成

ルーティングしたAPI::Baseクラスを定義します。
prefixにapiを定義することでhttp://[host]/apiのpathを構成することができます。

■ /app/apis/api/base.rb

module API
  class Base < Grape::API
    # Baseクラスのpathを定義する
    prefix 'api'

  end
end

上記のBaseクラスと同じようにv1ディレクトリ配下にもbase.rbを作成します。
versionを定義することでhttp://[host]/api/v1のpathを構成することができます。
formatAPIのレスポンスデータのフォーマットをjsonに指定しています。

■ /app/apis/api/v1/base.rb

module API
  module V1
    class Base < Grape::API
      version 'v1'
      format :json
    end
  end
end

先ほど作成したAPI::BaseAPI::V1::Baseをマウントします。

■ /app/apis/api/base.rb

module API
  class Base < Grape::API
    # Baseクラスのpathを定義する
    prefix 'api'

    # v1のBaseクラスをマウントする
    mount API::V1::Base
  end
end

apiクラスを作成

v1ディレクトリ配下にusersクラスを作成します。

resourceapiのpathを定義しています。

■ /app/apis/api/v1/users.rb

module API
  module V1
    class Users < Grape::API
      # `users`resource配下にすることで
      # /api/v1/usersのapiとしてアクセスできる
      resource :users do
        # GET http://[host]/api/v1/users
        get do
          User.all
        end

        # パラメタバリデーション
        params do
          requires :id, {type: Integer, desc: 'user id.'}
         end
         # GET http://[host]/api/v1/users/[user_id]
         get ':id' do
          User.find(params[:id])
         end
      end
    end
  end
end

API::V1::UsersAPI::V1::Baseにマウントします。

■ /app/apis/api/v1/base.rb

module API
  module V1
    class Base < Grape::API
      version 'v1'
      format :json
    end
    mount API::V1::Users
  end
end

サンプルデータを作成してAPIにアクセス

ローカル環境でRailsサーバを起動します。

rails server

http://localhost:3000/usersにアクセスして、画面上から適当にデータを作成します。

f:id:inon29:20150907082655p:plain

  • http://localhost:3000/api/v1/usersにアクセスするとUsersテーブルのデータの全件が取得できます。

f:id:inon29:20150907082705p:plain

  • http://localhost:3000/api/v1/users/1にアクセスするとUsersテーブルのデータの1件目が取得できます。

f:id:inon29:20150907082714p:plain

参考サイト

http://kzy52.com/entry/2014/11/07/084023 http://qiita.com/hkusu/items/2ca0323cc276ab31e926

【cocos2d-x】【CocoStudio】CocoStudioを使って画面のレイアウトを読み込む

環境

cocos2d-x 3.2
CocoStudio for Mac 1.0.0 Bata

CocoStudioでレイアウトを作成する

まずは、CocoStudioにて任意の画面を作成します。
以下では、画面に一つボタンを置いています。

cocos.png

上記、赤枠の名前は、各UIパーツのをソースコード上から取得する時に必要になるので、
わかりやすい名前にしておきます。

レイアウトファイルの読み込み

CocoStudioにて、cmd + Eでエクスポート。
[CocoStudioのプロジェクト]/Export配下のに作成したキャンバスのディレクトリが出力されているので、それをそのままXCodeへimportします。

インポートしたレイアウトを以下のようにソースコード上で読み込みます。

#include "cocostudio/CocoStudio.h"
// UIパーツを使用するためにimportしておく
#include "CocosGUI.h"

auto node = cocostudio::timeline::NodeReader::getInstance()->createNode("[jconファイル名]");

// 画面に配置
addChild(node);

子ノードを取得する

レイアウト上に配置した、子ノードを取得するにはgetChildByName()を使います。
ここで指定するオブジェクト名は、CocoStudio上で名前に指定した文字列です。

auto button = (Button *)scene->getChildByName("[オブジェクト名]");


階層が深い位置のオブジェクトを取得するには、以下のヘルパーメソッドを使います。

utils::findChildren([探索対象ノード], [オブジェクト名])

Buttonイベントを取得するとき

参考までに、ui::Buttonクラスのタッチイベントを取得するには以下のようにします。

button->addTouchEventListener([&](Ref *ref, Widget::TouchEventType type) {
    if (type == Widget::TouchEventType::ENDED) { // タッチアップを取得する
    }
});

参考

http://qiita.com/kyokomi/items/9e3dea9325047940f3e0
http://iphone-labo.blogspot.jp/2014/08/cocos2dx-v32node.html
http://giginet.hateblo.jp/entry/2014/08/29/185519
http://raharu0425.hatenablog.com/entry/2014/06/20/154718

【cocos2d-x】cocos2d-x 3.X系を使ってみる① (セットアップ)

最近、ゲーム開発に携わる機会があり、
ゲーム開発の大手プラットホームであるcocos2d-xについて触れてみました。

cocos2d-xは、2Dゲームをつくることに優れたプラットホームで、
開発の方法はわかり易いのです。 ただ、2.X系と3.X系で大きな違いがあったり、ビルド関連でハマったり色々つまずいたのでメモしておきます。

cocos2d-xのセットアップ

1.ダウンロード

cocos2d-xを以下のページからダウンロードします。

公式ページ

ダウンロードしたcocos2d-xのディレクトリを任意の場所に配置します。

2.setup.pyを実行

手順1でダウンロードしたディレクトリへ移動し、直下にあるsetup.pyファイルを実行する。

python setup.py

色々と足りないパスを聞かれるので、設定されていないパスがあれば設定を行います。
これで、開発する為の準備が完了しました。

3.新規プロジェクトを作成する

作成するゲームのプロジェクトを作成します。
cocos2d-x 3.X系で新規プロジェクトを作成するには、cocosコマンドを使用します。

cocos new [プロジェクト名] -p [パッケージ名] -l [開発言語(cpp)] -d [プロジェクトを置くディレクトリ名]

■ 各引数の簡単な解説

-p Androidプロジェクトのパッケージ名。iOSBundle identifierも共通かなと思ってましたが、ここは変わりませんでした。

-l 開発言語を指定します。対応する言語は次のとおりです。 c++ => "cpp", lua => "lua", javascript => "js"

-d プロジェクトを配置するRootディレクトを指定します

作成されたディレクトリを確認すると、以下のようなディレクトリが作成されているかと思います。
これが各開発環境のプロジェクトディレクトリになります。

proj.android
proj.ios_mac
proj.linux
proj.win32
proj.wp8-xaml

プロジェクトをビルド

プロジェクトが正しく作成できたら、試しにプロジェクトをビルドします。
ここでは、iOS用のプロジェクトをビルドしてみます。

cocos run -s [プロジェクトディレクトリ] -p ios

ビルドが成功すると、iPhoneシュミレータが起動して以下のような画面が表示されているかと思います。

f:id:inon29:20140916001114p:plain


これで、開発のための準備ができました。
今回は、iOSAndroidのアプリをターゲットにしているので、 今後は、各OSでのハマりどころなどを押さえていきたいと思います。

Apache用のVirtualHostの設定メモ(for Mac)

■ 環境

Mac OSX 10.9.3 (Mavericks)

Apacheの関連ファイルパス

httpd.conf

/private/etc/apache2/httpd.conf

アクセスログ

/private/var/log/apache2/access_log

エラーログ

/private/var/log/apache2/error_log

各ユーザー設定ファイル

/private/etc/apache2/extra/httpd-userdir.conf
/private/etc/apache2/users/[ユーザー名].conf

1.httpd.confの設定

ユーザーの設定ファイルは、以下のファイルに記述します。

/private/etc/apache2/users/[ユーザー名].conf

設定内容は、以下の通りです。

<VirtualHost *:80>
    ServerName [ホスト名]
    DocumentRoot [ドキュメントルートPath]
    <Directory [ディレクトリパス ※ドキュメントルートと同じ]>
        Options Indexes FollowSymLinks Includes ExecCGI
        AllowOverride all
        Options -MultiViews
        Order allow,deny
        Allow from all 
    </Directory>
</VirtualHost>

2.hostsの編集

以下のファイルにhostの設定を記載します。

/etc/hosts


127.0.0.1  [ホスト名]

【Android】他のアプリケーションの上にViewを表示する

Androidのアプリの中には、 何かのアプリを起動中もずっと画面上に残り続けるアプリがあると思います。

今回は、上記のように他のアプリケーション上で、
自分のアプリケーションのViewを表示する方法について試して見ました。


Androidのviewは、複数のレイヤーによって構成されており、
通常のアプリケーションのレイヤーは、他のアプリケーションの画面上に乗せることができません。

常に画面上に自分のアプリケーションのViewを表示し、
他のアプリケーションが起動中も自分のアプリのViewを画面上に表示するためには、WindowManagerを使ってレイヤーを指定する必要があります。


通常のアプリケーションの上に載せられるレイヤーには、
幾つか種類がありますが、今回は、画面上のViewに対してタッチイベントを取得したかったので、WindowManager.LayoutParams.TYPE_SYSTEM_ALERTを使用しました。


Androidのレイヤーについては、下記の参考リンクにて詳しく説明されているので、ご参照ください。

使い方は、
以下のようにまずWindowManagerインスタンスを取得し、
画面上に出したいViewをWindowManagerに対してaddView()します。

その際に、LayoutParamsにWindowManager.LayoutParams.TYPE_SYSTEM_ALERTを指定することで、
通常のアプリケーションよりも上にViewを表示することが可能となります。

WindowManager.LayoutParams  Params = new WindowManager.LayoutParams(
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,       // アプリケーションのTOPに配置
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |  // フォーカスを当てない(下の画面の操作がd系なくなるため)
            WindowManager.LayoutParams.FLAG_FULLSCREEN |        // OverlapするViewを全画面表示
            WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,  // モーダル以外のタッチを背後のウィンドウへ送信
            PixelFormat.TRANSLUCENT);  // viewを透明にする

WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
windowManager.addView([OverlapView], mOverlapViewParams);


通常のアプリケーションよりも上にViewを表示するため、
使用後は、removeView()でViewを削除しないと、
画面上にずっと出続けてしまう危険がありますので注意が必要です。

[サンプル]

以下のサンプルでは、
ActivityからServiceをBindし、ServiceからWindowmanagerに対して、
ViewをaddView()しています。

アプリケーションが終了したタイミングで、
画面上に表示しているViewも一緒に削除されるようにServiceのunbindのタイミングで、
removeView()を呼び画面からViewを削除しています。

■ OverlapService.java

public class OverlapService extends Service {
    
    private WindowManager mWindowManager;
    private FrameLayout mOverlapView;
    private WindowManager.LayoutParams mOverlapViewParams;
    
    // Serviceに接続するためのBinderクラスを実装する
    public class LocalBinder extends Binder {
        //Serviceの取得
        OverlapService getService() {
            return OverlapService.this;
        }
    }
    // Binderの生成
    private final IBinder mBinder = new LocalBinder();
    
    @Override
    public IBinder onBind(Intent intent) {
        // Service接続時に呼び出される
        // 戻り値として、Serviceクラスとのbinderを返す。
        Log.i("", "onBind" + ": " + intent);
        return mBinder;
    }
 
    @Override
    public void onRebind(Intent intent){
        // Unbind後に再接続する場合に呼ばれる
        Log.i("", "onRebind" + ": " + intent);
    }
 
    @Override
    public boolean onUnbind(Intent intent){
        // Service切断時に呼び出される
        // onUnbindをreturn trueでoverrideすると次回バインド時にonRebildが呼ばれる
        return true;
    }
    
    @Override
    public void onCreate() {
        super.onCreate();
        
        mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        LayoutInflater layoutInflater = LayoutInflater.from(this);
        mOverlapView = new FrameLayout(getApplicationContext());
        ((FrameLayout)mOverlapView).addView(layoutInflater.inflate(R.layout.Overlap_content_view, null));
        mOverlapViewParams = new WindowManager.LayoutParams(
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,       // アプリケーションのTOPに配置
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |  // フォーカスを当てない(下の画面の操作がd系なくなるため)
            WindowManager.LayoutParams.FLAG_FULLSCREEN |        // OverlapするViewを全画面表示
            WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, // モーダル以外のタッチを背後のウィンドウへ送信
            PixelFormat.TRANSLUCENT);  // viewを透明にする

        mWindowManager.addView(mOverlapView, mOverlapViewParams);
    }
    
    @Override
    public void onDestroy() {
        // ServiceがunbindされるタイミングでViewも削除して上げる
        mWindowManager.removeView(mOverlapView);
        super.onDestroy();
    }
}

■ MainActivity(呼び出し元)

public class MainActivity extends Activity {
    
    // Serviceとのインターフェースクラス
    private ServiceConnection mConnection = new ServiceConnection() {
        OverlapService mBindService;
        public void onServiceConnected(ComponentName className, IBinder service) {
            // Serviceとの接続確立時に呼び出される。
            // service引数には、Onbind()で返却したBinderが渡される
            mBindService = ((OverlapService.LocalBinder)service).getService();
            //必要であればmBoundServiceを使ってバインドしたServiceへの制御を行う
        }
         
        public void onServiceDisconnected(ComponentName className) {
            // Serviceとの切断時に呼び出される。
            mBindService = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        Button bindBtn = (Button) findViewById(R.id.bind_service_button);
        bindBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent i = new Intent(MainActivity.this,OverlapService.class);
                bindService(i, mConnection, Context.BIND_AUTO_CREATE);
            }
        });
        
        Button unbindBtn = (Button) findViewById(R.id.unbind_service_button);
        unbindBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Serviceをunbindする
                unbindService(mConnection);
            }
        });
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // Serviceをunbindする
        unbindService(mConnection);
    }
}

(参考リンク)
http://techbooster.org/android/ui/13182/
http://matsuhilog.blogspot.jp/2011/06/typesystemalert.html

初めてのRuby On Rails③ (メールを送る)

Railsには、メールを送信するための仕組みとしてActionMailerという機能があります。
今回は、ActionMailerを使用して、Gmailのメール送信を試してみます。

1.環境設定

設定ファイルにGmailの設定を記入します。

config/environments/development.rb

  config.action_mailer.default_url_options = { :host => "localhost", :port => 3000 }
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
    :address => "smtp.gmail.com",
    :port => 587,
    :domain => 'example.com',
    :user_name => "[ユーザー名]",
    :password => "[Gmailのパスワード]",
    :authentication => 'plain',
    :enable_starttls_auto => true,
  }

2.ActionMailerクラスを作成

ActionMailerクラスを生成するには、以下のコマンドを使用します。

rails generate mailer [Mialerクラス名] [アクション名]

例えば、messageというクラスにtestアクションを定義した場合は、
次のようにコマンドを実行します。

rails generate mailer message test

自動的に以下のファイルが生成されます。

create  app/mailers/message.rb
invoke  erb
create    app/views/message
create    app/views/message/test.text.erb
create    app/views/message/test.html.erb
invoke  test_unit
create    test/mailers/message_test.rb
create    test/mailers/previews/message_preview.rb

ActionMailerクラスは、app/mailers/配下に作成されています。

app/mailers/message.rb

class Message < ActionMailer::Base
  # デフォルトの送信もとアドレス
  default from: "[送信もとアドレス]"

  # Subject can be set in your I18n file at config/locales/en.yml
  # with the following lookup:
  #
  #   en.message.hellp.subject
  #
  def hellp
    @greeting = "Hi"

    mail to: "[送信先アドレス]"
  end
end

送信されるメッセージの本文は、
app/views/message/test.html.erbに定義します。

<h1>Message#test</h1>

<p>
  <%= @greeting %>, find me in app/views/message/test.html.erb
</p>

3. 送信確認

railsのコンソールからメールを送信してみます。

rails console

Message.test.deliver

app/mailers/message.rbに定義した、
送信先のメールアドレスにメールが送信されていれば、正常に動作しています。

初めてのRuby On Rails② (scaffoldジェネレータ)

Rubyには、コードを自動生成するジェネレータが用意されています。
今回は、データベースのCRUD
データの作成(Create)、読み出し(Read)、更新(Update)、削除(Delete)
の処理をを自動的に作成してくれる scaffoldジェネレータを使ってみました。

1.railsプロジェクトの作成

今回は、サンプルとして「bookshelf」というプロジェクトを作成します。

rails new bookshelf

2.scaffoldコマンドの実行

scaffoldジェネレータは、rails generateから始まるコマンドを使用します。

rails generate scaffold [モデル名] ([カラム名]:[データ型]…)

引数としてモデル名を渡して実行しますが、
ここでオプションとしてカラムを追加することも可能です。

カラムは、[カラム名]:[データ型] の書式で記載します。

今回は、「name」(string)と「description」(text)というカラムを持った「Book」というモデルを作成してみます。

rails generate scaffold Book name:string description:text

以下のように、
関連するモデル、ビュー、コントローラ、テストなどが、自動で生成されます。

invoke  active_record
      create    db/migrate/20140603151844_create_books.rb
      create    app/models/book.rb
      invoke    test_unit
      create      test/models/book_test.rb
      create      test/fixtures/books.yml
      invoke  resource_route
       route    resources :books
      invoke  scaffold_controller
      create    app/controllers/books_controller.rb
      invoke    erb
      create      app/views/books
      create      app/views/books/index.html.erb
      create      app/views/books/edit.html.erb
      create      app/views/books/show.html.erb
      create      app/views/books/new.html.erb
      create      app/views/books/_form.html.erb
      invoke    test_unit
      create      test/controllers/books_controller_test.rb
      invoke    helper
      create      app/helpers/books_helper.rb
      invoke      test_unit
      create        test/helpers/books_helper_test.rb
      invoke    jbuilder
      create      app/views/books/index.json.jbuilder
      create      app/views/books/show.json.jbuilder
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/books.js.coffee
      invoke    scss
      create      app/assets/stylesheets/books.css.scss
      invoke  scss
      create    app/assets/stylesheets/scaffolds.css.scss


まずは、確認のためにhttp://127.0.0.1:3000/booksへ接続してみます。

f:id:inon29:20140604214335p:plain

ActiveRecord::PendingMigrationErrorというエラーが表示されました。

これは、rails generate scaffoldコマンドによってプロジェクトの関連ファイルは生成されましたが、
実際のDBのテーブルは、生成されていないため発生するエラーです。

railsがデフォルトで生成されるsqliteDBのファイルを確認しても、テーブルが作成されていないことがわかります。

(dbファイルの場所)

[puroject_path]/db/development.sqlite3

3.DBへマイグレーションファイルを適用する

DBへテーブル情報を反映されるためには、
scaffoldコマンドは、が自動で作成してくれる「マイグレーションファイル」を使います。
マイグレーションファイル」は、モデルの変更内容をDBへと反映する為の情報が記載されているファイルです。

(マイグレーションファイルの場所)

[puroject_path]/db/migrate/[マイグレーションファイル]

今回は、bookというモデルを作成したので、

[puroject_path]/db/migrate/20140603151844_create_books.rb

というファイルが作成されています。

class CreateBooks < ActiveRecord::Migration
  def change
    create_table :books do |t|
      t.string :name
      t.text :description

      t.timestamps
    end
  end
end

ファイルの中身を見てみるとrails generate scaffoldコマンドで指定したテーブルの、
createメソッドが記述されています。

railsでは、モデル名に対して複数型の名称のDBテーブル名をつけるルールになっているため
 テーブル名はbooksとなっています。

この、マイグレーションファイルをDBへ反映されるためには、
以下のコマンドを実行します。

rake db:migrate

先ほど確認したsqliteDBファイルを再度確認すると
booksというテーブルができていることが確認できます。

sqlite> .tables
books              schema_migrations
sqlite>

もう一度http://127.0.0.1:3000/booksへ接続し表示を確認すると、
エラーが消え以下のような画面が表示されます。

f:id:inon29:20140604214403p:plain

画面から、New Bookのリンクをクリックすると以下のような画面へ遷移し、
booksテーブルにレコードを追加することができます。

f:id:inon29:20140604215853p:plain

その他、レコードの編集など、
基本的なデータ操作をscaffoldジェネレータを使うだけで簡単に実装することが可能です。