NSRailsを使ってみた
7月1122日に行う 「クラウドxスマフォ時代のRuby on Rails入門」 セミナーで使うデモアプリを作るために、NSRails を使ってみました。
概要
NSRails の使い方は https://github.com/dingbat/nsrails に書かれているように、iOS側に Rails と同じモデルを用意し、 モデルクラスの 取得(remoteAll: , remoteObjectWithID: ...)やCRUD(remoteCreate: , remoteUpdate: , remoteDestroy: ...) メソッドを呼び出すだけで、Railsサーバーとのデータやり取りが出来ます。また通信は、同期、非同期をサポートしています。
使ってみて分かったこと
1. Pod は GitHub から
Railsサーバーが起動してないときにiOS側で通信するとへんなエラーで落ちますが GitHubのmasterでは修正されているのでPodfileは以下のようにした方が良いでしょう。
pod 'NSRails', :git => 'https://github.com/dingbat/nsrails.git'
2. autogen/generate がモデルの雛形を作ってくれて超便利
NSRailsのGitHubにある autogen/generate コマンドで Railsプロジェクトのパスを指定すると、そのプロジェクト内の全モデルに対応する iOS側のモデルの雛形を作ってくれて、超便利です。
./autogen/generate RailsProject Making directory RailsProject.gen/ Writing files to /Users/yy/tmp/nsrails/autogen/RailsProject.gen + Category.h + Category.m + Customer.h + Customer.m ...
3. iOS側モデルは必ずしもRailsのモデルと同じで無くても良い
Rails側とiOS側での処理分担は思案のしどころですが、必ずしも一致される必要はありません。iOS側モデルは Rails側のview (〜.json.jbuilder) と対応していれば良いので、iOS側を簡単にしたい場合はviewでたくさんの情報を作り、iOS側はそれを利用する事もできます。今回作っているデモアプリは下のように、簡単な処理も Rails 側でやって渡しています。
json.array!(@timelines) do |timeline| json.extract! timeline, :id, :user_id, :caption, :created_at json.name timeline.user.name json.title photo_title(timeline) json.photo_url full_url(timeline.photo.url) if timeline.photo.present? json.photo_thumb_url full_url(timeline.photo.thumb.url) if timeline.photo.present? end
- TimeLine.h
#import <NSRails/NSRails.h> @interface Timeline : NSRRemoteObject @property (nonatomic, strong) NSString *caption; @property (nonatomic, strong) NSNumber *userId; @property (nonatomic, strong) NSString *name; @property (nonatomic, strong) NSString *title; @property (nonatomic, strong) NSString *photoUrl; @property (nonatomic, strong) NSString *photoThumbUrl; @property (nonatomic, strong) NSString *imageData; @property (nonatomic, strong) NSDate *createdAt; @end
4. 戻さなくて良いプロパティー(インスタンス変数)が指定出来る
NSRailsのCookbook にiOS側モデルに付いて色々と書かれています。 shouldSendProperty: メッソドをオーバーライトして、iOSからRailsには戻さないプロパティーを指定できます。これを使い、3. のように余計な情報をRailsから送ってもらってる場合は、それらをshouldSendProperty: に指定しておけば、通信コストを小さくできます。
- TimeLine.m
#import "Timeline.h" @implementation Timeline - (BOOL) shouldSendProperty:(NSString *)property whenNested:(BOOL)nested { if ([property isEqualToString:@"title"] || [property isEqualToString:@"name"] || [property isEqualToString:@"photoUrl"] || [property isEqualToString:@"photoThumbUrl"]) return NO; return [super shouldSendProperty:property whenNested:nested]; } @end
その他、プロパティー名をRails側と変えたいとか、型を変えるとか・・・ 色々な事ができますので NSRailsのCookbook は一読しておくと良いと思います。
5. 画像データをJSONで送りたい
今回のアプリでは、iOS側から画像データをRails側に送る必要があるので、StackOverflow 等を調べ以下のようなコードを書きました。
.... Timeline *newTimeline = [[Timeline alloc] init]; newTimeline.caption = post.caption; newTimeline.imageData = [self base64FromImage:post.image]; [newTimeline remoteCreateAsync:^(NSError *error) { if (error) { NSLog(@"+++ err %@", error); } else { NSLog(@"+++ ok : id=%@", newTimeline.remoteID); } }]; .... - (NSString *)base64FromImage:(UIImage *)image { return [UIImagePNGRepresentation(image) base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; }
Rails側は、image_dataの送られて来たBase64エンコードされた画像データをデコードし StringIOで受け取り、その IO Stream を file_field に対応する photo フィールド に代入しています。この後の処理は Carrierwave で行っています。Carrierwaveの処理では IO Stream に path メソッドが必要なようなので、特異メソッドとして追加してます。
ネット上にあるサンプルコードでは Tempfileクラスを使って、テンポラリーファイルに書くコードを多く見かけましたが、それほど大きな画像は送られてこないので、ファイルを作らない StringIO を使ってみました。
def create @timeline = Timeline.new(timeline_params_with_image_data) if @timeline.save ..... def timeline_params_with_image_data parameters = timeline_params if params[:timeline][:image_data] parameters[:photo] = parse_image_data(params[:timeline][:image_data]) end parameters end def parse_image_data(image_data) tempfile = StringIO.new(Base64.decode64(image_data)) def tempfile.path "/tmp/upload.png" end ActionDispatch::Http::UploadedFile.new(tempfile: tempfile, content_type:'image/png', filename:'iphone.png') end
6. Basic認証
Rails側でiOSアプリを認証するの部分は、今回は実装が簡単な Basic認証を使ってみました。生パスワードを送っているので実際のアプリで使う場合は SSL 通信が必要ですね。
NSRailsには Basic認証用ヘッダーを送る機能を持っているのでNSRConfigに ユーザー、パスワードを設定するだけです。
[NSRConfig defaultConfig].appUsername = login.email; [NSRConfig defaultConfig].appPassword = login.password;
Rails側は 定番の devise を使っています。コードは deviseのHow To にあったものです。
class TimelinesController < ApplicationController before_action :authenticate .... private .... def authenticate if from_iphone? authenticate_or_request_with_http_basic do |username,password| resource = User.find_by(email: username) if resource && resource.valid_password?(password) sign_in :user, resource end end else authenticate_user! end end end
7. ログの制御
デフォルトではコンソールに通信内容も表示されます、画像を送ったりすると大変な量のログが表示されるので、ログを制限した方が良いと思います。手っ取り早いのは NSRailsの NSRails.h ファイルの以下の定義を変える事です
// As undefined, NSRails will log nothing // #define NSRLog 1 //As 1, NSRails will log HTTP verbs with their outgoing URLs, as well as any server errors #define NSRLog 2 //As 2, NSRails will also log any JSON going out/coming in
8. 通信中表示
NSRConfigのmanagesNetworkActivityIndicator を YES に設定すると通信中にステータスバーに 通信中のグルグルを表示してくれるので便利
[NSRConfig defaultConfig].managesNetworkActivityIndicator = YES;
どんな物が出来たか興味のあるかたは 「クラウドxスマフォ時代のRuby on Rails入門」 セミナー にご参加下さい。転職・人材系会社でのセミナーですが・・・あまり気にしなくて良いと思います。