Servletの非同期処理
JAX-RSの非同期処理を実装しようと思ったけど、そもそもServletの非同期処理を実装したことなかったのでそっちの復習から。
参考記事
こちらの記事を参考にしてみました。
ちなみに参考にしたページではServletContextListenerでThreadPoolExecutor作ってる。要るのかわからんのでひとまずなしで。
動作確認
Tomcatを起動してブラウザでこんなかんじでアクセスする。
http://localhost:8080/async_servlet/AsyncLongRunningServlet?time=3000
この場合処理に3秒かかるわけですが、サーバのコンソールをみるとサーブレットスレッドがすぐ終了してるのがわかる。
AsyncLongRunningServlet Start::Name=25::ID=25
AsyncLongRunningServlet End::Namehttp-nio-8080-exec-3::ID=25::Time=13ms.
ちょっと疑問
サーバのコンソールを見ると、StartとEndの出力でスレッド名が変わってるんだけどこういうもんなのかな。
20分くらいでできるVagrantによるRails開発環境の構築
はじめに
Railsの環境をパパっと作りたい。でもVagrantもChefもよくわからない。そんな私でもすぐできました。
こちらが参考になりました。
以下内容をまとめてみました。
Using Vagrant for Rails Development
Overview
読み終えるまでにかかる時間は20分くらい
Vagrantは開発環境のセットアップを自動化するツールです。あなたのコンピュータ上に仮想環境を構築します。Vagrantを使えば開発環境とプロダクションサーバの整合性を保ったり、同僚と同じ環境で開発したりできるようになります。
仮想環境上に構築したRails開発環境は、ホストコンピュータの設定がいかなる状態になろうとも、同じ状態を維持できます。もし、1年後に開発プロジェクトを再度稼働する必要が生じた場合にも、数分でプロジェクトを起動できます。これは大変便利なことです。
ここでは仮想環境上の開発環境の自動構築にChefを使用します。ホストコンピュータ上にRubyと依存するパッケージがセットアップされているか注意してください。
セールストークは十分でしょう。それでは始めましょうか。
Setting Up Vagrant
Vagrantで仮想環境上でRailsアプリケーションを起動するためにはゲストOSを起動しなければなりません。そのため、ホストコンピュータ上で1GB、2GB程度のRAMが利用できることを確認しておいてください。
最初のStepはコンピュータ上にVagrantとVirtualBoxをインストールしてください。
VirtualBoxは仮想マシンを実行するソフトウェアです。VirtualBoxはバックグラウンドで動作するヘッドレスなソフトウェアとして起動できるので、ゲストOSとSSHを使ってインタラクティブにやりとりすることができます。
- vagrant-vbguest VirtualBox Guest Additionsを自動的にインストールします
- vagrant-librarian-chef マシンの起動時にchefを自動的に実行します
vagrant plugin install vagrant-vbguest vagrant plugin install vagrant-librarian-chef
インストールには少し時間がかかるでしょう。
Create the Vagrant config
ChefをセットアップするためにRailsプロジェクトのトップに移動し、次のコマンドを実行します。
cd MY_RAILS_PROJECT # Change this to your Rails project directory vagrant init touch Cheffile
ここで作成されたVagrantfileとCheffileをカスタマイズすることになります。
Your Cheffile
ここではCheffileを定義します。CheffileはRailsのGemfileとよく似ています。このファイルにはプロジェクトに必要なChefのクックブックを定義します。後ほど作成するVagrantfileによって、Vagrantはこれらのクックブックを参照することで環境を構築するようになります。
Cheffileに次のコードをペーストすると良いでしょう。
site "http://community.opscode.com/api/v1" cookbook 'apt' cookbook 'build-essential' cookbook 'mysql' cookbook 'ruby_build' cookbook 'nodejs', git: 'https://github.com/mdxp/nodejs-cookbook' cookbook 'rbenv', git: 'https://github.com/fnichol/chef-rbenv' cookbook 'vim'
Your Vagrantfile
Vagrantfileには仮想マシン上のOSとChefの設定を定義します。
ゲストOSにはUbuntu 14.04 trusty 64-bitを使用します(32-bitがよければtrusty32でもOKです)。ここでは2GBのメモリを定義しています。続いてrails serverを実行した際、ブラウザでアクセスしやすくするため、ホストOS上の3000番ポートを仮想マシンの3000番ポートにフォワードするように設定しています。最後に仮想マシン内にChef、Ruby2.1.2、MySQLをセットアップしています。
Vagrantfileは次のように変更してください。
# -*- mode: ruby -*- # vi: set ft=ruby : VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| # Use Ubuntu 14.04 Trusty Tahr 64-bit as our operating system config.vm.box = "ubuntu/trusty64" # Configurate the virtual machine to use 2GB of RAM config.vm.provider :virtualbox do |vb| vb.customize ["modifyvm", :id, "--memory", "2048"] end # Forward the Rails server default port to the host config.vm.network :forwarded_port, guest: 3000, host: 3000 # Use Chef Solo to provision our virtual machine config.vm.provision :chef_solo do |chef| chef.cookbooks_path = ["cookbooks", "site-cookbooks"] chef.add_recipe "apt" chef.add_recipe "nodejs" chef.add_recipe "ruby_build" chef.add_recipe "rbenv::user" chef.add_recipe "rbenv::vagrant" chef.add_recipe "vim" chef.add_recipe "mysql::server" chef.add_recipe "mysql::client" # Install Ruby 2.1.2 and Bundler # Set an empty root password for MySQL to make things simple chef.json = { rbenv: { user_installs: [{ user: 'vagrant', rubies: ["2.1.2"], global: "2.1.2", gems: { "2.1.2" => [ { name: "bundler" } ] } }] }, mysql: { server_root_password: '' } } end end
Running Vagrant
以上でVagrantとChefの設定は完了です。Vagrantで仮想マシンを起動してsshで接続してみましょう。
# The commented lines are the output you should see when you run these commands vagrant up #==> default: Checking if box 'ubuntu/trusty64' is up to date... #==> default: Clearing any previously set forwarded ports... #==> default: Installing Chef cookbooks with Librarian-Chef... #==> default: The cookbook path '/Users/chris/code/test_app/site-cookbooks' doesn't exist. Ignoring... #==> default: Clearing any previously set network interfaces... #==> default: Preparing network interfaces based on configuration... # default: Adapter 1: nat #==> default: Forwarding ports... # default: 3000 => 3000 (adapter 1) # default: 22 => 2222 (adapter 1) #==> default: Running 'pre-boot' VM customizations... #==> default: Booting VM... #==> default: Waiting for machine to boot. This may take a few minutes... # default: SSH address: 127.0.0.1:2222 # default: SSH username: vagrant # default: SSH auth method: private key # default: Warning: Connection timeout. Retrying... #==> default: Machine booted and ready! #==> default: Checking for guest additions in VM... #==> default: Mounting shared folders... # default: /vagrant => /Users/chris/code/test_app # default: /tmp/vagrant-chef-1/chef-solo-1/cookbooks => /Users/chris/code/test_app/cookbooks #==> default: VM already provisioned. Run `vagrant provision` or use `--provision` to force it
vagrant ssh #Welcome to Ubuntu 14.04 LTS (GNU/Linux 3.13.0-24-generic x86_64) # # * Documentation: https://help.ubuntu.com/ # # System information disabled due to load higher than 1.0 # # Get cloud support with Ubuntu Advantage Cloud Guest: # http://www.ubuntu.com/business/services/cloud # # #vagrant@vagrant-ubuntu-trusty-64:~$
初めてvagrant upコマンドを実行するときは時間がかかります。これはChefの設定ファイルに従って仮想マシンをプロビジョニングするためです。次回以降のvagrant upコマンドはChefを実行する必要なくなるため、起動に時間はかからなくなるでしょう。
VagrantfileやCheffileを編集した場合は、次のコマンドを実行してマシンを再設定しなければなりません。
vagrant provision
Using Rails inside Vagrant
Vagrantは/vagrantフォルダを仮想マシンとホストOS間で共有します。cd /vagrantで移動してlsコマンドを叩けばRailsアプリケーションのファイルを確認できるでしょう。
このディレクトリでbundleコマンドを実行すればgemをインストールできますし、rake db:create && rake db:migrateを実行すればデータベースを構築しマイグレーションも行えます。
このディレクトリでrails serverコマンドを実行すればRailsアプリケーションが3000番ポートで起動します。いつもどおりlocalhost:3000にアクセスすればRailsアプリケーションを操作できます。
Conclusion
Vagrantはポータブルな開発環境を作成するソフトウェアツールです。そこにはあなたのコードを含むこともできます。Chefを使ってシンプルな設定を施すだけで、ほとんど時間をかけずに仮想マシンを起動することができます。
もし、あなたが再びVagrantマシンをセットアップする必要がでてきたり、同僚がセットアップを必要とする場合は、vagrant upコマンドを実行するだけで仮想マシンを手に入れることができます。
5分で学ぶMaven
これは何か
Maven – Maven in 5 Minutesを参考にまとめたものです。
Maven – Maven in 5 Minutes
Installation
MavenはJavaのツールです。そのため以降の作業を続けるにはJavaのインストールは事前に済ませておいてください。Mavenのダウンロードはこちらから、インストールについてはこちらを参考してください。
Mavenのインストールが完了したら、ターミナルやコマンドプロンプトから次のように入力します。
% mvn --version
そうするとインストールされたMavenのバージョンが表示されるでしょう。たとえばこんな具合に。
Apache Maven 3.2.3 (33f8c3e1027c3ddde99d3cdebad2656a31e8fdf4; 2014-08-12T05:58:10+09:00) Maven home: /usr/local/Cellar/maven/3.2.3/libexec Java version: 1.7.0_04-ea, vendor: Oracle Corporation Java home: /Library/Java/JavaVirtualMachines/1.7.0.jdk/Contents/Home/jre Default locale: ja_JP, platform encoding: UTF-8 OS name: "mac os x", version: "10.9.4", arch: "amd64", family: "mac"
ネットワークの状態によっては追加の設定が必要な場合もあります。その場合はGuide to Configuring Mavenを参考にしてください。Windowsの場合はWindows Prerequisitesを確認しておくと良いでしょう。
Creating a Project
Mavenでプロジェクトを開始するためにディレクトリを作成する必要があります。コマンドラインで次のMavenゴールを実行します。
mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
Mavenをインストールしてすぐに上記のコマンドを実行すると時間がかかる場合があります。これは最新のアーティファクト(プラグイン、jar、その他のファイルなどの総称)をローカルリポジトリにダウンロードする必要があるためです。場合によっては、タイムアウトする可能性があるので、その場合は2、3回コマンドを実行する必要があるでしょう。
コマンドが終了するとarchetype:generateゴールによって、artifactIdに指定した値と同名のディレクトリが生成されます。続いて生成されたディレクトリに移動します。
% cd my-app
生成されたディレクトリはMavenの提唱するstandard project structureに従った構成になっています。
my-app% tree . ├── pom.xml └── src ├── main │ └── java │ └── com │ └── mycompany │ └── app │ └── App.java └── test └── java └── com └── mycompany └── app └── AppTest.java
src/main/javaディレクトリにはプロジェクトのソースコードを格納します。 src/test/javaディレクトリにはテストコードを格納します。pom.xmlには後述するプロジェクトのProject Object Modelを記述します。
The POM
pom.xmlファイルはMavenのプロジェクト管理の核となるファイルです。プロジェクトをビルドするための多くの重要事項を含む唯一のファイルです。POMは複雑でややこしいことを実現できますが、今はすべてを理解する必要はありません。このプロジェクトのPOMファイルは次のようになっているでしょう。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany.app</groupId> <artifactId>my-app</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>my-app</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> </project>
What did I just do?
Mavenのゴールであるarchetype:generateを実行しました。その際にいくつかのパラメータを渡しました。接頭辞のarchetypeはプラグインであり、プラグインはゴールを含んでいます。もしAntを知っているなら、Antのタスクと似ていると感じたかもしれません。このゴール(archetype:generate)は指定したarchetypeに基づいてシンプルなプロジェクトを生成しました。今のところプラグインについては、一般的な目標を持ったゴールのコレクションであると考えておけば良いでしょう。たとえばjboss-maven-pluginの場合は、JBossの様々な処理を扱うためのプラグインです。
Build the Project
% mvn package
上記のコマンドを実行するといくつかのアクションが表示され、最終的には次のような表示になります。
... [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ [INFO] Total time: 2 seconds [INFO] Finished at: Thu Jul 07 21:34:52 CEST 2011 [INFO] Final Memory: 3M/6M [INFO] ------------------------------------------------------------------------
最初に実行したコマンド(archetype:generate)と違って、2つ目のコマンドはpackageという単語一つでシンプルになっています。これはゴールではなくフェーズと呼びます。フェーズは、ビルドライフサイクルにおける一つのステップを意味します。一連のフェーズは順序付けられています。Mavenはフェーズを受け取ると、指定されたフェーズまでの一連のフェーズをすべて実行します。たとえばcompileフェーズを指定した場合、実際に実行されるフェーズは次のようになります。
- validate
- generate-sources
- process-sources
- generate-resources
- process-resources
- compile
パッケージされた最新のJARファイルを使ってテストを行うには次のように実行します。
java -cp target/my-app-1.0-SNAPSHOT.jar com.mycompany.app.App
次のようなお約束の出力を確認できるでしょう。
Hello World!
Running Maven Tools
Maven Phases
デフォルトのライフサイクルで実行されるフェーズは以下のとおりです。
- validate:プロジェクトを検証して必要な情報が利用可能か確認する。
- compile:プロジェクトのソースコードをコンパイルする。
- test:ユニットテスティングフレームワークを使ってコンパイルされたソースコードをテストする。これらのテストはパッケージやデプロイに不要なものとすべきである。
- package:コンパイル済みのコードをJARファイルのような配布フォーマットにパッケージする。
- integration-test:インテグレーションテスト環境にパッケージをデプロイする。
- verify:パッケージを検証し、品質判定基準を満たしているか確認する。
- install:ローカルリポジトリにパッケージをインストールし、多のプロジェクトから依存関係を解決できるようにする。
- deploy:最新のパッケージをリモートリポジトリにコピーする。これにより、他の開発者やプロジェクトでパッケージを共有できるようになる。
また、Mavenには上記のデフォルトのリストの他に2つのライフサイクルがあります。
- clean:ビルドによって作成されたアーティファクトを削除する。
- site: このプロジェクトのドキュメントサイトを生成する。
フェーズは実際にはゴールにマッピングされます。フェーズごとに実行される具体的なゴールは、プロジェクトのパッケージタイプに依存しています。たとえばプロジェクトのタイプがJARの場合、packageフェーズでjar:jarゴールを実行します。同様にプロジェクトタイプがWARの場合は、packageフェーズでwar:warゴールを実行します。
面白いことにフェーズとゴールは順序立てて実行できます。
mvn clean dependency:copy-dependencies package
このコマンドはプロジェクトをクリーンし、依存関係をコピーします。それからパッケージを作成します(もちろんパッケージまでの前フェーズはすべて実行されます)。
Generating the Site
mvn site
このフェーズはプロジェクトのpomの情報に基づいてサイトを生成します。生成されたドキュメントはtarget/siteで確認できます。
Conclusion
駆け足での紹介でしたが、このガイドを通じてMavenの多彩さに興味を持ってもらえると幸いです。これはあくまでクイックスタートガイドだという点だけは念を押しておきます。とはいえ、Mavenの詳細を学ぶ準備は整いました。Maven Getting Started Guideも参考にしてください。
Rails4.1でSpring、Rspec、Guardを使う
はじめに
Rails4.1でテスト環境を構築する方法を調べてました。以下のブログ(英語、、)が参考になったので紹介します。
Setup Rails 4.1, Spring, Rspec, and Guard - Girders
Setup Rails 4.1, Spring, Rspec, and Guard
Rails4.1では、Springを使うことでrailsコマンドやrakeコマンドの起動を素早く実行することができます。Springはアプリケーションのバックグラウンドで動作し、アプリケーション起動時のもろもろの動作をサポートします。Springに関連する情報を見つけるのに苦労してので、ここにまとめておきます。
私はテストフレームワークにRSpec、テストランナーのイベントウォッチャーにGuardを使っています。Springを使えば、エディタで編集したファイルを速やかにテストしてくれるようになります。
はじめに検証用のアプリケーションを作るところから始めましょう。もちろん、既存のアプリケーションでもこれから紹介する方法で上手くいくと思います。Rails 4.1.0.beta1 on Ruby 2.1.0p0で動作検証済みです。
% rails new myapp -T % cd myapp % vim Gemfile
Gemfileを編集し次のgemを追記します。
- rspec-rails - Rails用のRSpecテスティングフレームワーク
- spring-commands-rspec - springにrspecコマンドを追加する。Railsアプリケーションのプレローダ機能も含む
- guard-rspec - rspecを実行するためにGuardファイルウォッチャ
- rb-fsevent - OS/Xでのみ必要。Mac OS/X FSEvents APIを通じでファイルの変更を監視する
Gemfileには次のように記述するとよいでしょう。(もちろんgemのバージョンも記述してもかまいません)
group :development, :test do gem 'spring-commands-rspec' gem 'rspec-rails' gem 'guard-rspec' gem 'rb-fsevent' if `uname` =~ /Darwin/ end
"Bundle"をもう一度実行して、依存関係のあるgemをインストールします。
% bundle install
Setup Spring
springコマンドは、springアプリケーションランナーを介してコマンドを実行します。デフォルトでは、railsコマンドrakeコマンドのみ設定されています。ここにプラグイン(gem)を追加すれば、rspecコマンドを実行することができるようになります。
springにはbinstubというサブコマンドがあります。このサブコマンドを使えばmyapp/binのスクリプトを更新できます。
springでrspecコマンドを利用できるようにするために、次のコマンドを実行します。
% spring binstub --all * bin/rake: spring already present * bin/rspec: generated with spring * bin/rails: spring already present
springがバックグラウンドで起動しているかどうかはstatusサブコマンドで確認できます。springが有効となっているコマンドを使ってサーバを起動してみましょう。
springの初回起動時はRailsのイニシャライザを実行するので時間がかかります。springはアプリケーションサーバを起動したら、結果を速く返すためにプロセスをフォークしています。
% spring status Spring is not running. % rails g ... % spring status Spring is running: 89168 spring server | myapp | started 5 secs ago 89170 spring app | myapp | started 5 secs ago | development mode
springサーバはshellのセッションが終了するときに自動的にシャットダウンされます。明示的にシャットダウンしたい場合は、次のようにコマンドを入力します。
% spring stop Spring stopped.
springを利用するのに必須となる知識はこれだけです。
Setting up Rspec and Guard
このプロセスも通常のやり方と同じです。
% rails g rspec:install create .rspec create spec create spec/spec_helper.rb % guard init 11:32:14 - INFO - Writing new Guardfile to /Users/allen/src/myapp/Guardfile 11:32:14 - INFO - rspec guard added to Guardfile, feel free to edit it % vim Guardfile
上記のコマンドでrspecとguardに必要なファイルをセットアップします。次に、guardに対してrspecを使うときにspring runnerを使用することを伝えなければなりません。Guardfileを編集して、rspecコマンドをspring rspecコマンドに変更します。
guard :rspec do
上記の部分を次のように変更します。
guard :rspec, cmd:"spring rspec" do
Running
(ここで紹介する実行方法は、binstubs、shell、aliasなど、コマンドの実行方法はあなたの環境に依存します。bundle execやbin/railsのように環境に合わせて解釈してください)
springを使用するとrspecがどの程度速くなるのかチェックしてみましょう。ここではtimeコマンドを使います。
% time spring rspec ... spring rspec 0.12s user 0.07s system 6% cpu 3.126 total % time spring rspec ... spring rspec 0.12s user 0.06s system 30% cpu 0.600 total
上記の実行結果は一つのscaffoldリソースをもつシンプルなアプリケーションで試したものです。アプリケーションが大きくなるに連れて、初回起動に必要な時間は長くなります。つまり、節約できる時間も増えていくことでしょう。
TDDを始めるなら、ターミナルセッションでguardを起動しましょう。
% guard 11:48:02 - INFO - Guard is using TerminalTitle to send notifications. 11:48:02 - INFO - Guard::RSpec is running 11:48:02 - INFO - Guard is now watching at '/Users/allen/src/myapp' [1] guard(main)>
アプリケーションのファイルがguardによって監視され、specやsuiteが変更されるたびに、spring rspecコマンドが実行されるようになります。
gemファイルの追加など、キャッシュされているローカルの依存関係を更新したい場合は、guardとspringを再起動する必要があります(guardは"ctrl-D"、springはspring stopを実行するなど)。guardを再起動すればspringも再起動されるので、依存関係がリロードすることができるでしょう。
Parse - Relations
Relations Guide
リレーションには3つの種類があります。One-to-oneリレーションは1つのオブジェクトを他のオブジェクトに関連付けます。One-to-manyリレーションは1つのオブジェクトに複数のオブジェクトに関連付けます。many-to-manyリレーションは複数のオブジェクト間の複雑な関連を表します。
Relationships in Parse
Parseでリレーションを定義するには4つの方法があります。
- Pointers (one-to-one、one-to-manyリレーション向け)
- Arrays (one-to-many、many-to-manyリレーション向け)
- Parse Relations (many-to-manyリレーション向け)
- Join Tables (many-to-manyリレーション向け)
One-to-Many Relationships
one-to-manyリレーションのPointers、Arraysを検討する場合はいくつかの検討事項があります。まずリレーションにどれだけの数のオブジェクトが関連するかを検討します。リレーションの"many"サイドにたくさんの数(100件を超えるような)のオブジェクトを含むなら、Pointersを選択してください。100件より少ない数のオブジェクトを扱うならArraysは便利です。とくに、親オブジェクトの取得と同時にすべての子オブジェクトを扱いたいケースで役に立つでしょう。
Using Pointers for One-to-Many Relationships
ゲームアプリケーションを開発しているとしましょう。ゲームではプレイするごとにスコアや結果を記録します。Parse上ではGameオブジェクトの中にこのデータを保存します。ゲームが上手くいくと、個々のプレイヤーはシステム内に数千のGameオブジェクトを格納します。このように、大きな数のリレーションが発生する可能性がある状況においては、Pointersを選択すると良いでしょう。
このゲームアプリケーションでは、Parse UserにすべてのGameオブジェクトを関連付けるものとします。次のように実装できます。
ParseObject game = new ParseObject("Game"); game.put("createdBy", ParseUser.getCurrentUser());
Parse Userによって生成されたGameオブジェクトを取得するクエリは次のようになります。
ParseQuery<ParseObject> gameQuery = ParseQuery.getQuery("Game"); gameQuery.whereEqualTo("createdBy", ParseUser.getCurrentUser());
特定のGameオブジェクトを生成したParse Userを取得したい場合は、createdByキーでルックアップします。
// say we have a Game object ParseObject game = ... // getting the user who created the Game ParseUser createdBy = game.getUser("createdBy");
多くの場合において、Pointersはone-to-manyリレーションを実現するための良い選択肢となるでしょう。
Using Arrays for One-to-Many Relations
Arraysはone-to-manyリレーションにおいて、オブジェクトの数が少ないとわかっている場合に有効です。Arraysは、includeKeyパラメータを使うことでメリットが生まれます。パラメータを適用すると"one-to-many"リレーションにおける"one"オブジェクトを取得する際に、"many"に該当するすべてのオブジェクトを取得するこができます。ただし、関連するオブジェクトの数が多くなるとレスポンスタイムがかかってしまいます。
ゲームアプリケーションの場合、プレイヤーが手に入れた武器を記録する必要があるでしょう。この武器の数は1ダース程度に収まるものとします。このようなケースでは、武器の数が極端に多くならないことがわかっています。また、プレイヤーは画面に表示する武器の並び順を指定できるとしましょう。扱う配列の数が少ないとわかっていて、プレイするごとにユーザが並び順を設定したいような場合においてArraysは上手く動作します。
Parse UserオブジェクトにweaponsListフィールドを作成してみましょう。
// let's say we have four weapons ParseObject scimitar = ... ParseObject plasmaRifle = ... ParseObject grenade = ... ParseObject bunnyRabbit = ... // stick the objects in an array ArrayList<ParseObject> weapons = new ArrayList<ParseObject>(); weapons.add(scimitar); weapons.add(plasmaRifle); weapons.add(grenade); weapons.add(bunnyRabbit); // store the weapons for the user ParseUser.getCurrentUser().put("weaponsList", weapons);
Weaponオブジェクトのリストを取得する場合は次のようになります。
ArrayList<ParseObject> weapons = ParseUser.getCurrentUser().get("weaponsList");
one-to-manyリレーションにおいて、"one"オブジェクトをフェッチする際に"many"オブジェクトをすべてフェッチしたいケースもあるでしょう。
Parseはこのようなケースを想定して、ParseQueryにincludeKeyパラメータ(Androidの場合はincludeメソッド)を提供しています。これにより、Parse User取得時にWeaponオブジェクトのリストをフェッチできます。
// set up our query for a User object ParseQuery<ParseUser> userQuery = ParseUser.getQuery(); // configure any constraints on your query... // for example, you may want users who are also playing with or against you // tell the query to fetch all of the Weapon objects along with the user // get the "many" at the same time that you're getting the "one" userQuery.include("weaponsList"); // execute the query userQuery.findInBackground(new FindCallback<ParseUser>() { public void done(List<ParseUser> userList, ParseException e) { // userList contains all of the User objects, and their associated Weapon objects, too } });
one-to-manyリレーションの"many"側から、"one"オブジェクトを取得できます。たとえば、特定のWeaponを持っているParseUserを見つけたいなら、次のようにクエリに制約を実装します。
// add a constraint to query for whenever a specific Weapon is in an array userQuery.whereEqualTo("weaponsList", scimitar); // or query using an array of Weapon objects... userQuery.whereEqualTo("weaponsList", arrayOfWeapons);
Many-to-Many Relationships
続いてmany-to-manyリレーションシップを見てみましょう。読書アプリケーションを開発しているとして、Book ObjectsとAuthor Objectsのようなモデルがあるとします。Authorは複数のBookを記述し、Bookは複数のAuthorを持ちます。このようなmany-to-manyシナリオでは、Arrays、Parse Relations、Join Tableの生成といった選択肢があります。
これらの選択のポイントは、2つのエンティティ間のリレーションシップにメタデータを保持するかどうかです。メタデータを保持しないなら、Parse RelationやArraysを使うことが好ましいでしょう。一般的に配列を使うと、パフォーマンスが高くなり、より少ないクエリで済むようになります。many-to-manyリレーションシップのいずれかの配列要素数が100を超える場合は、Pointersと同じ理由でParse RelationやJoin Tablesを使う方が好ましいでしょう。
一方、リレーションにメタデータを保持する場合は、セパレートテーブル("Join Table")を用意して両方のリレーションを格納するようにします。これはリレーションについての情報であり、片方のリレーションのオブジェクトについての情報ではないという点に注意してください。メタデータとして、Join Tableに定義するものには次のようなものがあります。
- リレーションが作成された日時
- リレーションを作成したユーザ
- リレーションが参照された回数
Parse Relations
Parse Relationsを使うと、BookといくつかのAuthorオブジェクトの間にリレーションを定義できます。Data Browserでは、Bookオブジェクトにauthorsという名前のリレーション型の列を作成できます。
その後、BookオブジェクトにいくつかのAuthorsオブジェクトを関連付けます。
// let’s say we have a few objects representing Author objects ParseObject authorOne = ParseObject authorTwo = ParseObject authorThree = // now we create a book object ParseObject book = new ParseObject("Book"); // now let’s associate the authors with the book // remember, we created a "authors" relation on Book ParseRelation<ParseObject> relation = book.getRelation("authors"); relation.add(authorOne); relation.add(authorTwo); relation.add(authorThree); // now save the book object book.saveInBackground();
あるBookを書いたAuthorのリストを取得するクエリは次のようになります。
// suppose we have a book object ParseObject book = ... // create a relation based on the authors key ParseRelation relation = book.getRelation("authors"); // generate a query based on that relation ParseQuery query = relation.getQuery(); // now execute the query
あるAuthorの寄稿したBookの一覧を取得したいこともあるでしょう。リレーションの逆を取得するクエリを作成します。
// suppose we have a author object, for which we want to get all books ParseObject author = ... // first we will create a query on the Book object ParseQuery<ParseObject> query = ParseQuery.getQuery("Book"); // now we will query the authors relation to see if the author object we have // is contained therein query.whereEqualTo("authors", author);
Using Join Tables
リレーションについてもっと知りたくなるようなケースもあるでしょう。たとえば、userが他のuserをフォローするようなuser間のfollowing/followerのリレーションです。これはソーシャルネットワークでポピュラーなものです。私たちのアプリケーションでは、User AがUserBをフォローしたという情報だけでなく、いつフォローを開始したかという情報も知りたいとしましょう。このような情報は、Parse Relationに含むことができません。このような情報を保存するためには、リレーションシップが記録されるセパレートテーブルを作成しなければなりません。このテーブルをFlollowテーブルと呼びましょう。Followテーブルにはfromカラムとtoカラムがあり、これらはParse Userへのポインタを保持します。これらのリレーションと並んで、dateという名前のDate型のカラムを追加します。
2人のユーザのフォロー関係を保存する場合、from、to列、date列を適切に入力して、Followテーブルに行を追加します。
// suppose we have a user we want to follow ParseUser otherUser = ... // create an entry in the Follow table ParseObject follow = new ParseObject("Follow"); follow.put("from", ParseUser.getCurrentUser()); follow.put("to", otherUser); follow.put("date", Date()); follow.saveInBackground();
フォローしている全員を取得したいなら、Followテーブルに次のようなクエリを実行します。
// set up the query on the Follow table ParseQuery<ParseObject> query = ParseQuery.getQuery("Follow"); query.whereEqualTo("from", ParseUser.getCurrentUser()); // execute the query query.findInBackground(newFindCallback<ParseObject>() { public void done(List<ParseObject> followList, ParseException e) { } });
同様にフォロワーを取得する場合も簡単です。
// set up the query on the Follow table ParseQuery<ParseObject> query = ParseQuery.getQuery("Follow"); query.whereEqualTo("to", ParseUser.getCurrentUser()); // execute the query query.findInBackground(newFindCallback<ParseObject>() { public void done(List<ParseObject> followList, ParseException e) { } });
Using Arrays for Many-to-Many Relations
Arraysは、One-to-Manyリレーションで使用したように、Many-to-Manyリレーションでも使用できます。リレーションの片方のすべてのオブジェクトは、Array型のカラムを持ち、リレーションの他方のいくつかのオブジェクトを保持します。
読書アプリケーションにおけるBookとAuthorオブジェクトを考えてみましょう。Bookオブジェクトは、AuthowオブジェクトのArrayを含むでしょう(authorsのようなカラム名で)。このシナリオではArraysは有効に働きます。なぜなら、Authorオブジェクトが100件を超える可能性が非常に低いからです。このような理由からBookオブジェクトにArrayを定義します。結果的に、Authorは100冊以上のBookと関連付けることもできます。*1
BookとAuthor間のリレーションを保存する例を見てみましょう。
// let's say we have an author ParseObject author = ... // and let's also say we have an book ParseObject book = ... // add the song to the song list for the album book.put("authors", author);
AuthorリストはArrayであるため、Bookのフェッチ時にincludeKey(Androidの場合はincludeメソッド)を使うことができます。そうするとbookのフェッチ時にauthorsも合わせて取得できます。
// set up our query for the Book object ParseQuery bookQuery = ParseQuery.getQuery("Book"); // configure any constraints on your query... // tell the query to fetch all of the Author objects along with the Book bookQuery.include("authors"); // execute the query bookQuery.findInBackground(newFindCallback<ParseObject>() { public void done(List<ParseObject> bookList, ParseException e) { } });
この時点で、BookオブジェクトからすべてのAuthor Objectsを取得できます。
ArrayList<ParseObject> authorList = book.getList("authors");
最後に、あなたはAuthorを知っていて、そのAuthorの寄稿したすべてのBookオブジェクトを取得したいとします。次のようにクエリに制約をかけます。
// set up our query for the Book object ParseQuery bookQuery = ParseQuery.getQuery("Book"); // configure any constraints on your query... booKQuery.whereEqualTo("authors", author); // tell the query to fetch all of the Author objects along with the Book bookQuery.include("authors"); // execute the query bookQuery.findInBackground(newFindCallback<ParseObject>() { public void done(List<ParseObject> bookList, ParseException e) { } });
One-to-One Relationships
Parseでは、one-to-oneリレーションは、あるオブジェクトを2つのオブジェクトに分離したいケースに使用します。このようなケースは稀ですが、次のようなケースが考えられます。
- ユーザデータの可視性に制限をかけたい場合。このようなケースでは、他のユーザに公開しても構わないユーザ情報と、他のユーザに公開したくないプライベートな情報に分離します(ACLを使ってプロテクトします)。
- サイズによるオブジェクトの分離。オブジェクトの最大サイズである128Kを超えてしまうような場合は、データの一部を分離するためにセカンダリオブジェクトを定義します。データを分離しなければならないような大規模なオブジェクトを設計するのは避けるべきでしょう。このような問題を回避できない場合は、Parse Fileによる大規模データの保存を検討してください。
ここまで読んでくれてありがとう。このように複雑になってしまうことは申し訳なく思っています。一般的にデータモデリングは複雑になりがちです。良く言えば、人間関係に比べればまだマシでしょうか。*2
Parse - Cloud Code
What is Cloud Code?
Parseのビジョンは、モバイルアプリを開発するデベロッパーから、サーバーの取り扱いをなくすことです。複雑なアプリケーションの場合は、ときとしてモバイルデバイス上で実行できないロジックが必要になります。Cloud Codeはこのような課題を解消します。
Cloud Codeの使い方は簡単です。なぜなら、アプリの開発で利用してきたJavaScript SDKをそのまま利用できるからです。モバイルデバイス上で動作するのか、Parse Cloud上で動作するのかという違いだけです。Cloud Codeはアップデートすると、モバイル環境へすぐに反映できるので、アプリケーションのリリースを待つ必要がなくなります。これにより、アプリケーションの更新が簡単になり、新機能も素早く追加できようになるでしょう。
あなたがモバイル開発に熟知しているとしても、Cloud Codeの理解しやすさ、簡単さを実感してもらえることでしょう。
Cloud Codeドキュメント
Parse - Cloud Code - Getting Started - Murayama blog.
Parse - Cloud Code - Cloud Functions - Murayama blog.
Parse - Cloud Code - Background Jobs - Murayama blog.
Parse - Cloud Code - Custom Webhooks - Murayama blog.
Parse - Cloud Code - Logging from Cloud Code - Murayama blog.
Parse - Cloud Code - Networking - Murayama blog.
Parse - Cloud Code - Command Line Tool
参照元
https://parse.com/docs/cloud_code_guide#clt
Command Line Tool
ここまでクラウド上にコードをデプロイするParseのコマンドラインツールを見てきました。コマンドラインツールには、他にも幾つか役に立つものが用意されています。コマンドラインツールのインストール手順については、Installing the Toolを参照してください。
Introduction
Parseでは、同じコードを複数のアプリケーションにデプロイできます。これにより、"development"アプリケーション、"production"アプリケーションのような使い分けが可能です。開発中のコードをproductionアプリケーションとして配布する前に、developmentアプリケーションでテストできます。
parse newコマンドで指定したアプリケーションが1つ目のデフォルトアプリケーションとなります。parse newコマンドを除くすべてのコマンドはアプリケーション名を引数にとることができます。
Deploying
新しいリリースをデプロイするには次のように実行します。
$ parse deploy New release is named v1
この結果、新しいコード(cloud/main.js)がParseクラウドにPUSHされ、デフォルトアプリケーションにデプロイされます。追加した別のアプリケーションをターゲットとしてデプロイする場合は次のように実行します。
$ parse deploy "My Other App" New release is named v2
リリース時に-d、--descriptionオプションを指定してリリースノートを追加することもできます。
自動テストやデプロイといった他のスクリプトにparse deployコマンドを埋めることもできます。Parseコマンドラインツールは、コマンドの実行結果をexit codeとして返します。コマンドが正常に終了した場合は、exit codeに0が返ります。デプロイに失敗した場合は0以外の値が返ります。
Developing Cloud Code
developコマンドを使えばdevelopmentモードでコマンドラインツールを実行できます。developmentモードは、ログファイルを監視するようにソースディレクトリを監視し、アップデートを検出するとParseにデプロイします。
$ parse develop development E2013-03-19:20:17:01.423Z] beforeSave handler in release 'v1' ran for GameScore with the input: {"original": null, "update":{"score": 1337}} and failed validation with Each GamesScore must have a playerName New release is named v58 I2013-03-19T20:17:10.343Z] Deployed v58 with triggers: GameScore: before_save
他のコマンドと違って、developコマンドはPUSH対象のアプリケーション名を必ず指定する必要があります。これは、誤ってテストしていないコードをproductionアプリケーションに適用しないようにするためです。
Add a New Target
addコマンドを実行することで、新しいparseアプリケーションをターゲットとして追加できます。このコマンドは、Parse.comに登録したEメールアドレスとパスワードを要求します。それらを入力すると選択可能なアプリケーションのリストが表示されます。
$ parse add Email: pirate@gmail.com Password: 1:PiecesOfEightCounter 2:BootyDivider Select an App: 1
addコマンドには、オプション引数でアプリケーションのエイリアスを指定できます。このエイリアスは、アプリケーション名の代用として利用できます。
ほとんどのケースで、設定情報はglobal.jsonファイルに記録されます。しかし、チームの他のメンバーとシェアしたくない開発用のアプリケーションが必要なこともあるでしょう。そのような場合は、--localフラグを使うと、local.jsonファイルに記録することができます。global.jsonファイルはソース管理下に置き、local.jsonはローカルマシン内でのみ使用すると良いでしょう。
Rolling Back
parse rollbackを使うとリリースをロールバックできます。parse deployと同じようにオプション引数でターゲットを指定できます。
$ parse rollback Rolled back to v1
こうすることで、コードを1つ前のバージョンにロールバックできます。-r、--release=オプションを使えばリリース名を指定してロールバックすることもできます。
Reading the Logs
すべてのデプロイ、ロールバック、Cloud Codeのアクティベーションはログとして残ります。parse logコマンドを使えば、最新のログを取得できます。ログには2つの種類があります。
- INFO - すべてのログを含みます。
- ERROR - エラーログだけを含みます。
logコマンドはオプションでターゲットを引数にとります。また3つのオプションがあります。
- -n - 表示するログの行数(デフォルトは10行)
- --level/-l - ログレベルを指定(デフォルトはINFO)
- -f - tail -fのように振る舞う
$ parse log -n 1 I2012-07-10:13:37:00] beforeSave handler in release 'v1' ran for GameScore with the input: {"original": null, "update":{"score": 1337}} and failed validation with Each GamesScore must have a playerName
Listing Releases
releasesコマンドを使うと、Parseクラウドへのリリース履歴がリスト表示されます。Parseは最新の10件のリリースのみ記録しています。
Setting the SDK version
開発ディレクトリにおいて、Cloud Codeを利用する際のデフォルトのParse JavaScript SDKバージョンは、newコマンドを実行した時点の最新バージョンです。このバージョンを変更したい場合は、config/global.jsonのparseVersionを変更してください。