2012年12月5日水曜日

rubymotionとrailsの連携を試行錯誤した話

この記事はRubyMotion Advent Calendar 2012の5日目記事です。

rubymotionメリットのひとつに、サーバサイド(Rails)とクライアントサイドが同じ言語でかけることがあると思いますが、
思いの外Railsとどう連携していくかの情報が少ないような気がします。
なので他の人の参考になればと、個人的に試行錯誤したことを残しておこうと思います。

ちなみに、one minutesというアプリを、rubymotionで作りましたので、よければダウンロードしてください。

試行錯誤1、babble-wrapで都度処理を書く

先述のone minutesでは、表示させるニュース情報をサーバからjsonで取得するだけの単純作業なので、以下の様な感じで実装しました。

  1. BW::HTTP.get(SERVER_URL) do |response|  
  2.   if response.ok?  
  3.     @data = BW::JSON.parse(response.body.to_str)  
  4.   else  
  5.     App.alert(response.error_message)  
  6.   end  
  7. end  

実装はかなり楽でしたが、複数情報を取得したい先がある場合や、postを行うときには、かなり面倒なことになってしまいました。
それが↓です。

試行錯誤2、babble-wrapで都度処理を書く(その2)

ソーシャルアプリを作ろうとした際の実装です。
アプリ概要は、facebookみたいなものなので、記事の投稿、投稿に対するコメント、投稿に対するいいね、それぞれを表示させるタイムラインの実装が必要でした。
それが下記コード群です。

#記事読み込み
  1. BubbleWrap::HTTP.get("#{SERVER_URL}/entries.json?page=#{@page}"do |response|  
  2.   if response.ok?  
  3.     json = BubbleWrap::JSON.parse(response.body.to_str)  
  4.     unless json.count == 0  
  5.       @table_dates << json  
  6.       self.tableView.reloadData  
  7.       @page += 1  
  8.       @readMoreButton.enabled = true  
  9.       @readMoreButton.hidden = false if @page  
  10.     else  
  11.       @page = nil  
  12.       @readMoreButton.hidden = true  
  13.     end  
  14.   else  
  15.     App.alert(response.error_message)  
  16.    end  
  17. end  
#記事投稿
  1. BubbleWrap::HTTP.post("#{SERVER_URL}/entries.json", {payload: data})  
#コメント
  1. BubbleWrap::HTTP.post("#{SERVER_URL}/comments.json", {payload: data}) do |response|  
  2.    if response.ok?  
  3.      App.alert("コメントしました")  
  4.    elsif response.status_code.to_s =~ /40\d/  
  5.     App.alert("comment failed")  
  6.    else  
  7.     App.alert(response.error_message)  
  8.   end  
  9. end  
#いいね
  1. BubbleWrap::HTTP.post("#{SERVER_URL}/likes.json", {payload: data}) do |response|  
  2.    if response.ok?  
  3.       App.alert("likeしました")  
  4.     else  
  5.      App.alert(response.error_message)  
  6.    end  
  7.  end  

企画自体が没になったため、コレ以上コードは書いていないのですが、
重複が多く、メンテナンス性も非常に悪いものができてしまいました。

で、このあとECアプリを作ることになりました。
これまでの経験を踏まえ、もう少しRailsライクに実装したいと感じるように。

具体的には、
1.new、find、save、allなどで情報が取得や保存ができるように
2.バリデーションエラーは、その内容までも分かるように
3.ネットワークエラーなどがでたら、バリデーションエラーなどとは別に判別できるように

という理想を掲げ、作ってみた現実(ライブラリ)はこちら。
https://github.com/face-do/motion-rails-model

初めて作ったライブラリでもあり、非常にできはよくないのですが、それでも一応の理想は実現できました。
(といっても、babble-wrapのさらにwrapperライブラリだったりしますが。。。)

使い方としては、最初にアクセスしたいURLやbasic認証の情報を入力


  1. RM::Model.set_url("#{URL}/api/")  
  2. RM::Model.set_username("username")  
  3. RM::Model.set_password("password")  

その後モデル用のクラスを作り、ライブラリを継承。

  1. class Orders < RM::Model  
  2.   attr_accessor :name  
  3.    
  4.   def attributes_update(json)  
  5.     @name = json['name']  
  6.     super  
  7.   end  
  8. end  

これで、

  1. order = Orders.new  
  2. order.name = "hogehoge"  
  3. order.save do |x|  
  4.   if x.ok?  
  5.     App.alert("保存しました。")  
  6.   end  
  7. end  

とかすると、
railsに対して
/api/orders.json?name=hogehoge
のpostメソッドを実行するし、

  1. Orders.find(1) do |x|  
  2.   if x.ok?  
  3.     @order = x.body  
  4.   end  
  5. end  

で、
/api/orders/1.json
のgetをしたりします。

またgetしてきた、Objectに対してsaveをすると、
/api/orders/1.json
に対してputをするようになってます。

これを作った後、メタプログラミングrubyを読みまして、
もう少し抽象化ができそうな気がしているので、機会を見て修正していく予定です。

2012年10月29日月曜日

今ウェブサイトを作るなら必須のアイコン画像サイズ一覧


漏れがないようにまとめてみました。

サイト全体
favicon
16×16

facebook(OGP)対策
200x200

ウェブクリップアイコン(ios、android)対策
114×114(iPhone、iPod touchのRetina)
57×57(iPhone、iPod touchの非Retina)
144×144(iPadのRetina)
72×72(iPadの非Retina)

一括作成サービスができてくれるといいなあ。

2012年9月29日土曜日

rubymotion用ライブラリの作り方

ライブラリのコードは予め書いておき、またrubygemsに公開するなら、
そのアカウントを取得しておく。

適当なフォルダで
# bundle gem motion-hogehoge
で、gemの雛形を作成

.gemspecに必要な項目を入力。
依存ライブラリがある場合は、ここに
gem.add_dependency "bubble-wrap", "~>1.1.4"
などと追記しておく。

次に、lib/motion-hogehoge.rbというファイルができているので、ここを修正。
rubymotionではrequireが対応していないので、rubymotion用に書き直す必要がある。
以下がサンプル

  1. unless defined?(Motion::Project::Config)  
  2.   raise "This file must be required within a RubyMotion project Rakefile."  
  3. end  
  4.   
  5. Motion::Project::App.setup do |app|  
  6.   Dir.glob(File.join(File.dirname(__FILE__), 'motion-hogehoge/*rb')).each do |file|  
  7.     app.files.unshift(file)  
  8.   end  
  9. end  

ライブラリのコードは、lib/motion-hogehoge/以下に入れておく。

rubygemsに登録する場合は、テストが通ってる必要がある。
とりあえず通すだけなら、以下でOK。

app/app_delegate.rbに
  1. class AppDelegate  
  2.   def application(application, didFinishLaunchingWithOptions:launchOptions)  
  3.     @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)  
  4.     @window.rootViewController = UIViewController.alloc.init  
  5.     @window.makeKeyAndVisible  
  6.     true  
  7.   end  
  8. end  
spec/main_spec.rbに
  1. describe "Application 'modeltest'" do  
  2.   before do  
  3.     @app = UIApplication.sharedApplication  
  4.   end  
  5.   
  6.   it "has one window" do  
  7.     @app.windows.size.should == 1  
  8.   end  
  9. end  

で、
#bundle install
#rake spec
でテストが通ることを確認する。

問題がなければ、
gem build motion-hogehoge.gemspec
で、gemファイルを作成し、

gem push motion-hogehoge-0.0.1.gem
で、rubygemsにアップする。

アップするときに取得したアカウント情報が聞かれるので入力すればOK。

2012年9月28日金曜日

Railsとjsonでやり取りするmotion-rails-modelというrubymotion用ライブラリを作ってみた

Railsと連携する際、babble-wrapを使っていたのですが、
取得先が複数になったときに面倒だったのでラッパーライブラリを作ってみました。
Active Record風のメソッドで、Rest風にアクセスをします。

github

使い方
rails側はindex,show,create,update,destoryをjsonで返信するようにします。 アプリ側では適当にmodelクラスを作って、motion-rails-modelを継承し、
attr_accessorとattributes_updateに項目を追加します。
  1. class Entries < RM::Model  
  2.   attr_accessor :title:description  
  3.     
  4.   def attributes_update(json)  
  5.     @title          = json['title']  
  6.     @description    = json['description']  
  7.     super  
  8.   end  
  9. end  
利用するときは、まずurlを設定します。
  1. class AppDelegate  
  2.   def application(application, didFinishLaunchingWithOptions:launchOptions)  
  3.     RM::Model.set_url("http://localhost:3000/")  
  4.   
  5.     @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)  
  6.     @window.rootViewController = RootViewController.alloc.init  
  7.     @window.makeKeyAndVisible  
  8.     true  
  9.   end  
  10. end  
あとは、
  1. Entries.all do |x|  
  2.   if x  
  3.     p x  
  4.   else  
  5.     p "error"  
  6.   end  
  7. end  
で、
http://localhost:3000/entriesにアクセスし
  1. Entries.find(1) do |x|  
  2.   if x  
  3.     p x  
  4.   else  
  5.     p "error"  
  6.   end  
  7. end  
で、
http://localhost:3000/entries/1
にアクセス。
  1. @entry = Entries.new  
  2. @entry.title = "title"  
  3. @entry.description = "description"  
  4. @entry.save do |x|  
  5.   if x  
  6.     p x  
  7.   else  
  8.     p "error"  
  9.   end  
  10. end  
で、
http://localhost:3000/entries
にpost。
  1. @entry.title = "title2"  
  2. @entry.save do |x|  
  3.   if x  
  4.     p x  
  5.   else  
  6.     p "error"  
  7.   end  
  8. end  
で、http://localhost:3000/entries/1
にput。
  1. @entry.destory do |x|  
  2.   if x  
  3.     p x  
  4.   else  
  5.     p "error"  
  6.   end  
  7. end  
で、http://localhost:3000/entries/1
にdeleteでアクセスするようになってます。

TODOとして
attributes_updateを設定しなくてもいいようにする。 エラー処理をもう少ししやすく。

2012年9月26日水曜日

現在のフォルダにあるファイルをリネームする

  1. files = Dir::entries(Dir::pwd)  
  2.   
  3. files.each do |f|  
  4. File.rename(f, f.sub(/aaa-/, '')) if f =~ /aaa-/  
  5. end  

csvを読み込んで、別のcsvの項目のうち2つ一致した項目を表示する

  1. # -*- coding: utf-8 -*-  
  2. require 'csv'  
  3.   
  4. a_csvs=[]  
  5. b_csvs=[]  
  6. succsess = []  
  7. failed = []  
  8. a_csvs_tmp = CSV.open('a.csv''r')  
  9. b_csvs_tmp = CSV.open('b.csv''r')  
  10.   
  11.   
  12. a_csvs_tmp.each{|x| a_csvs << x }  
  13. b_csvs_tmp.each{|x| b_csvs << x }  
  14.   
  15. a_csvs.each do |x|  
  16. a = b_csvs.select{|b| x[0] == b[0] and x[1] == b[1] }  
  17. unless a.empty?  
  18.   succsess << a  
  19. else  
  20.   failed << x  
  21. end  
  22. end  
  23.   
  24. puts "見つかったモノ"  
  25. p succsess.flatten  
  26. puts "失敗したモノ"  
  27. p failed.flatten  

2012年7月24日火曜日

iosとrailsでinstagramのクローンサービスを作る

iosとrailsでinstagramのクローンサービスを作る

railsと連携したネイティブアプリを作りたかったので、
instagramのクローンサービスを作ってみました。

完成度はかなり低く、多分全体実装の5%くらいのですが、
会員登録(ログイン)、登録者に紐付いたタイムラインの表示、写真(加工)、ユーザ検索、フォロー、アンフォロー
あたりまではできるようになっています。

ソースはいつものようにgithubに。
https://github.com/face-do/clonestagram

server側のコードと、client側のコードがセットではいってます。

実際に試す場合には、
画像のアップロードには、carrierwaveを使ってS3にあげているので、そのトークンを変更し、
またios側で通信先のURLをすべて変更してください。

会員登録(ログイン)の処理はserver側でdeviseを使っていて、
ios側がusernameとpasswordをjsonで送信すると、その結果をjsonで返してくれるので、
それをうけて適当に処理するようにしてます。

TODOとしては、
フィルタがしょぼいので調整する。
各種バグの修正。
ユーザ個別画面の修正。

参考:
http://wp.serpere.info/archives/2110
http://d.hatena.ne.jp/tomute/20091121/1258884514
http://d.hatena.ne.jp/sparkgene/20120422/1335075063
http://oneworld-inc.jp/blog/?p=148

iosのライブラリ
https://github.com/glassonion1/R9HTTPRequest
https://github.com/ldandersen/scifihifi-iphone/tree/master/security
http://stig.github.com/json-framework/

2012年3月23日金曜日

自分用のdropbox automatorが作れる!dropbox+herokuでtwitterに写真を自動投稿する方法

dropboxにファイルを上げると、自動的にfacebookやflickrに投稿するようにできるdropbox automatorというサービスがあります。
このサービスはtwitterへのphoto投稿が対応していないので、dropbox apiとherokuを使って、できるようにしてみたいと思います。
今回のファイルはgithubにあげておきました。

とりあえずgemをインストール
# vim Gemfile

下記を記述。
  1. source "http://rubygems.org"  
  2. gem "clockwork"  
  3. gem 'dropbox-sdk'  
  4. gem 'twitter'  


# bundle install

次にdropbox apiを使えるようにこちらから登録してください。
ログイン後、アプリ登録画面で「create an App」をクリック
適当にアプリ名と説明を入れて、「App folder」の方をチェックして、登録。

アプリの詳細画面でApp keyとApp secretを確認してください。
apiを使うためには、コレ以外にrequest_tokenとaccess_tokenが必要なのですが、
そのためにはブラウザでフォルダへの許可を取る必要があります。

なので、まずはその取得用のスクリプトを作成。

  1. # -*- coding: utf-8 -*-  
  2. require 'dropbox_sdk'  
  3.   
  4. APP_KEY = 'INSERT-APP-KEY-HERE'  
  5. APP_SECRET = 'INSERT-APP-SECRET-HERE'  
  6. ACCESS_TYPE = :app_folder  
  7. session = DropboxSession.new(APP_KEY, APP_SECRET)  
  8.   
  9. request_token = session.get_request_token  
  10.   
  11. authorize_url = session.get_authorize_url  
  12. puts "AUTHORIZING", authorize_url  
  13. gets  
  14.   
  15. access_token = session.get_access_token  
  16.   
  17. "request_token:", request_token  
  18. "access_token:", access_token  



上のスクリプトを一端起動してください。
途中urlが表示されると思いますので、それをブラウザに入力。
認証完了後、ターミナルに戻り、エンターを押してください。

request_tokenとaccess_tokenのkeyとsecretがそれぞれ表示されると思いますので、メモしておきます。

次に実際に動かすスクリプトの作成。

herokuのcedar stackでは、webの代わりにスクリプトを動かしっぱなしにできるので、それを利用します。
具体的には、clockworkというライブラリを使い、一定ごとにdropboxのapiを叩き、新規ファイルが登録されていれば、それを投稿という流れ。

設定用のファイルを作成。
# vim Procfile
  1. cron: bundle exec clockwork clock.rb  


コードは下記の通り。
  1. # -*- coding: utf-8 -*-  
  2. require 'clockwork'  
  3. require 'twitter'  
  4. require 'dropbox_sdk'  
  5. include Clockwork  
  6. @time = Time.now  
  7.   
  8. Twitter.configure do |config|  
  9.   config.consumer_key = 'XXXXXXXXXXXXXXXX'  
  10.   config.consumer_secret = 'XXXXXXXXXXXXXXXX'  
  11.   config.oauth_token = 'XXXXXXXXXXXXXXXX'  
  12.   config.oauth_token_secret = 'XXXXXXXXXXXXXXXX'  
  13. end  
  14.   
  15. APP_KEY = 'INSERT-APP-KEY-HERE'  
  16. APP_SECRET = 'INSERT-APP-SECRET-HERE'  
  17. ACCESS_TYPE = :app_folder  
  18. session = DropboxSession.new(APP_KEY, APP_SECRET)  
  19. session.set_request_token('REQUEST_TOKEN_KEY''REQUEST_TOKEN_SECRET')  
  20. session.set_access_token('ACCESS_TOKEN_KEY''ACCESS_TOKEN_SECRET')  
  21. client = DropboxClient.new(session, ACCESS_TYPE)  
  22.   
  23. handler do |job|  
  24.   filedata = nil  
  25.   file_metadata = client.metadata('/')  
  26.   filedata = file_metadata["contents"].map { |x| x if Time.parse( x["modified"]) > @time }  
  27.   filedata.each do |f|  
  28.    unless f == nil  
  29.     file = client.get_file(f["path"])  
  30.     filename = Time.now.to_i.to_s  
  31.     File.open("/tmp/" + filename, "w") {|f| f.write file}  
  32.     tmp = File.open("/tmp/" + filename, "rb")  
  33.     p tmp  
  34.     Twitter.update_with_media(@time.strftime("%F %H:%M"), tmp)  
  35.    end  
  36.   end  
  37.   @time = Time.now  
  38. end  
  39.   
  40. every(1.minutes, 'check')  


apiのキーはそれぞれ先程メモったものなどを入力してください。

あとはherokuに上げるだけ。
# git init
# git add .
# git commit -m 'first commit'

# heroku create --stack cedar
# git push heroku master
# heroku scale cron=1

ちゃんと動いているかどうか確認します。
# heroku ps
起動しているプロセスがcron.1だけのはず。

# heroku logs --tail
Triggering checkみたいなログがでているはずです。

あとは、dropboxに先程登録したアプリ名のファイルができているので、画像をコピーしてみます。(なぜかgitがうまく投稿できませんでした。。。)
twitterに自動投稿されていれば成功です。

ファイル名や種類、アップするフォルダで処理を変えるようにすれば、自分なりの「dropbox automator」がつくれます。

2012年3月19日月曜日

iphoneで撮った写真をherokuにアップロードする(iosアプリ)

herokuで無料のimage uploaderを作るの続き。

mongolabを使って、写真のアップローダを作れるようにしましたが、せっかくなのでiphoneから写真を直接あげられるようにしておきます。
写真をアップロードはhttpを使ってサーバにpostします。

postするサンプルを作っている人がいたので、これをベースにして使うことに。
https://github.com/tochi/HTTPFileUploadSample

HTTPFileUploadSampleViewController.hを以下のように修正
  1. @interface HTTPFileUploadSampleViewController : UIViewController <httpfileuploaddelegate, uitextfielddelegate="">  
  2. {  
  3.     IBOutlet UITextField *codeTextField;  
  4.     IBOutlet UIImageView *_imageView;  
  5. }  
  6.   
  7. - (IBAction)postButtonClicked:(id)sender;  
  8. - (IBAction)showCameraSheet:(id)sender;  
  9. @property (retain, nonatomic) IBOutlet UITextField *codeTextField;  
  10. </httpfileuploaddelegate,>  


HTTPFileUploadSampleViewController.mを以下のように修正
httpFileUpload postWithUriは自分のherokuのURLに修正してください。

  1. #import "HTTPFileUploadSampleViewController.h"  
  2.   
  3. @implementation HTTPFileUploadSampleViewController  
  4. @synthesize codeTextField = _codeTextField;  
  5.   
  6. - (void)dealloc  
  7. {  
  8.     [_imageView release];  
  9.     [_codeTextField release];  
  10.   [super dealloc];  
  11. }  
  12.   
  13. - (void)didReceiveMemoryWarning  
  14. {  
  15.   [super didReceiveMemoryWarning];  
  16. }  
  17.   
  18. #pragma mark - View lifecycle  
  19. - (void)viewDidLoad  
  20. {  
  21.   [super viewDidLoad];  
  22.     _codeTextField.returnKeyType = UIReturnKeyDone;  
  23.     _codeTextField.delegate = self;  
  24. }  
  25.   
  26. - (BOOL)textFieldShouldReturn:(UITextField *)textField {  
  27.     [_codeTextField resignFirstResponder];  
  28.     return YES;  
  29. }  
  30.   
  31. - (void)viewDidUnload  
  32. {  
  33.     [_imageView release];  
  34.     _imageView = nil;  
  35.     [codeTextField release];  
  36.     codeTextField = nil;  
  37.     [self setCodeTextField:nil];  
  38.   [super viewDidUnload];  
  39. }  
  40.   
  41. - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation  
  42. {  
  43.   return (interfaceOrientation == UIInterfaceOrientationPortrait);  
  44. }  
  45.   
  46. - (IBAction)postButtonClicked:(id)sender  
  47. {  
  48.   // Get image data.  
  49.   //UIImage *image1 = [UIImage imageNamed:@"Icon.png"];  
  50.     
  51.   // File upload.  
  52.   HTTPFileUpload *httpFileUpload = [[HTTPFileUpload alloc] init];  
  53.   httpFileUpload.delegate = self;  
  54.   [httpFileUpload setPostString:self.codeTextField.text withPostName:@"name"];  
  55.   [httpFileUpload setPostImage:_imageView.image withPostName:@"photo" fileName:@"Icon.png"];  
  56.   [httpFileUpload postWithUri:@"http://XXXX.herokuapp.com/users/photo.json"];  
  57.   [httpFileUpload release], httpFileUpload = nil;  
  58. }  
  59.   
  60. - (IBAction)showCameraSheet:(id)sender {  
  61.     // アクションシートを作る  
  62.     UIActionSheet*  sheet;  
  63.     sheet = [[UIActionSheet alloc]   
  64.              initWithTitle:@"Select Soruce Type"   
  65.              delegate:self   
  66.              cancelButtonTitle:@"Cancel"   
  67.              destructiveButtonTitle:nil   
  68.              otherButtonTitles:@"Photo Library", @"Camera", @"Saved Photos", nil];  
  69.     [sheet autorelease];  
  70.       
  71.     // アクションシートを表示する  
  72.     [sheet showInView:self.view];  
  73. }  
  74.   
  75. - (void)actionSheet:(UIActionSheet*)actionSheet   
  76. clickedButtonAtIndex:(NSInteger)buttonIndex  
  77. {  
  78.     // ボタンインデックスをチェックする  
  79.     if (buttonIndex >= 3) {  
  80.         return;  
  81.     }  
  82.       
  83.     // ソースタイプを決定する  
  84.     UIImagePickerControllerSourceType   sourceType = 0;  
  85.     switch (buttonIndex) {  
  86.         case 0: {  
  87.             sourceType = UIImagePickerControllerSourceTypePhotoLibrary;  
  88.             break;  
  89.         }  
  90.         case 1: {  
  91.             sourceType = UIImagePickerControllerSourceTypeCamera;  
  92.             break;  
  93.         }  
  94.         case 2: {  
  95.             sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;  
  96.             break;  
  97.         }  
  98.     }  
  99.       
  100.     // 使用可能かどうかチェックする  
  101.     if (![UIImagePickerController isSourceTypeAvailable:sourceType]) {    
  102.         return;  
  103.     }  
  104.       
  105.     // イメージピッカーを作る  
  106.     UIImagePickerController*    imagePicker;  
  107.     imagePicker = [[UIImagePickerController alloc] init];  
  108.     [imagePicker autorelease];  
  109.     imagePicker.sourceType = sourceType;  
  110.     imagePicker.allowsImageEditing = YES;  
  111.     imagePicker.delegate = self;  
  112.       
  113.     // イメージピッカーを表示する  
  114.     [self presentModalViewController:imagePicker animated:YES];  
  115. }  
  116.   
  117. - (void)imagePickerController:(UIImagePickerController*)picker   
  118.         didFinishPickingImage:(UIImage*)image   
  119.                   editingInfo:(NSDictionary*)editingInfo  
  120. {  
  121.     // イメージピッカーを隠す  
  122.     [self dismissModalViewControllerAnimated:YES];  
  123.     // オリジナル画像を取得する  
  124.     UIImage*    originalImage;  
  125.     originalImage = [editingInfo objectForKey:UIImagePickerControllerOriginalImage];  
  126.       
  127.     // グラフィックスコンテキストを作る  
  128.     CGSize  size = { 300, 400 };  
  129.     UIGraphicsBeginImageContext(size);  
  130.       
  131.     // 画像を縮小して描画する  
  132.     CGRect  rect;  
  133.     rect.origin = CGPointZero;  
  134.     rect.size = size;  
  135.     [originalImage drawInRect:rect];  
  136.       
  137.     // 描画した画像を取得する  
  138.     UIImage*    shrinkedImage;  
  139.     shrinkedImage = UIGraphicsGetImageFromCurrentImageContext();  
  140.     UIGraphicsEndImageContext();  
  141.       
  142.     // 画像を表示する  
  143.     _imageView.image = shrinkedImage;  
  144. }  
  145.   
  146. - (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker  
  147. {  
  148.     // イメージピッカーを隠す  
  149.     [self dismissModalViewControllerAnimated:YES];  
  150. }  
  151.   
  152. - (void)httpFileUpload:(NSURLConnection *)connection  
  153.       didFailWithError:(NSError *)error  
  154. {  
  155.   NSLog(@"%@", error);  
  156. }  
  157.   
  158. - (void)httpFileUploadDidFinishLoading:(NSURLConnection *)connection  
  159.                                 result:(NSString *)result  
  160. {  
  161.   NSLog(@"%@", result);  
  162.     UIAlertView *alert = [[UIAlertView alloc]  
  163.                           initWithTitle:@""   
  164.                           message:@"投稿完了しました。"   
  165.                           delegate:nil   
  166.                           cancelButtonTitle:@"OK"   
  167.                           otherButtonTitles:nil, nil];  
  168.     [alert show];  
  169.     [alert release];  
  170. }  
  171. @end  


次にここを参考にxibを作成。アクションシートを追加してください。(コードは上のものにすでに入ってます。)
http://news.mynavi.jp/column/iphone/001/index.html
同じような感じで、textfieldを追加してください。(codeTextField)

次にrails側。
こちらをベースに修正します。
https://github.com/face-do/heroku-image-uploader
UsersController.rbを以下の用に修正。
  1. def photo  
  2.   @user = User.new(:name => params[:name], :photo => params[:photo] )  
  3.   
  4.   respond_to do |format|  
  5.     if @user.save  
  6.       format.html { redirect_to @user, notice: 'User was successfully created.' }  
  7.       format.json { render json: @user, status: :created, location: @user }  
  8.     else  
  9.       format.html { render action: "new" }  
  10.       format.json { render json: @user.errors, status: :unprocessable_entity }  
  11.     end  
  12.   end  
  13. end  

config/routes.rbに以下を追記
post "users/photo" => 'users#photo'

app/controllers/application_controller.rbを以下のように修正。
  1. protect_from_forgery  :except => :photo  


で、herokuにあげて、iosアプリをiphoneに転送すれば、できます。
一応今回のファイルをgithubにあげておきました。
rails アプリのほう
iphoneクライアントの方

2012年3月18日日曜日

herokuで無料のimage uploaderを作る

herokuで無料のimage uploaderを作る

herokuは非常に便利ですが、read onlyなのでアップローダーを作ったりできません。
もしやろうとするとS3を使った方法が一般的のようですが、若干利用料金がかかってしまいます。
なので無料で作れる方法を考えてみました。

herokuでは画像を直接アップすることはできませんが、DBに直接保存することができます。
しかしherokuのデフォルトのものは、5MBしかありません。
そこでmongolabという、mongodbを240MBまで無料提供してくれるサービスを利用します。
なおmongolabはherokuにadd-onとして提供されているため、セットアップは簡単です。

githubにファイルをあげておいたので、参考にしてください。
https://github.com/face-do/heroku-image-uploader

まずはrailsプロジェクトを作成します。
ただしmongodbを使うため、active recordを切るようにしておきます。

# rails new uploader -O

次に、gemfileに以下を追記

  1. gem 'mongoid''~>2.1'  
  2.   
  3. gem 'bson_ext''~>1.3'  
  4.   
  5. gem 'carrierwave'  
  6.   
  7. gem 'carrierwave-mongoid':require => 'carrierwave/mongoid'  


# bundle install

で、mongodbに接続するための設定のひな形を作る。
# rails generate mongoid:config

config/mongoid.ymlのproductionを以下のように修正
  1. production:  
  2.   
  3.   uri: <%= ENV['MONGOLAB_URI'] %>  



carrierwaveの設定をする
#vim config/initializers/carrierwave.rb

以下を記述
  1. CarrierWave.configure do |config|  
  2.   
  3.   config.storage = :grid_fs  
  4.   
  5.   config.grid_fs_connection = Mongoid.database  
  6.   
  7.   config.grid_fs_access_url = "/images"  
  8.   
  9. end  


アップローダのひな形を作る
# rails g scaffold page title:string
# rails g uploader photo

app/models/user.rbは以下のように

  1. class User  
  2.   
  3.   include Mongoid::Document  
  4.   
  5.   field :title:type => String  
  6.   
  7.   mount_uploader :photo, PhotoUploader  
  8.   
  9. end  


app/uploaders/photo_uploader.rbは以下のように修正

  1. # encoding: utf-8  
  2.   
  3. class PhotoUploader < CarrierWave::Uploader::Base  
  4.   
  5.   include CarrierWave::RMagick  
  6.   
  7.   storage :grid_fs  
  8.   
  9.   def store_dir  
  10.   
  11.     "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"  
  12.   
  13.   end  
  14.   
  15.   version :thumb do  
  16.   
  17.     process :resize_to_limit => [200, 200]  
  18.   
  19.   end  
  20.   
  21. end  


app/views/users/_form.html.erbの
の上に以下を追記

  1. <div class="field">  
  2.   
  3.   <%= f.label :photo %>  
  4.   
  5.   
  6.   <%= image_tag( @user.photo_url ) if @user.photo? %>  
  7.   
  8.   <%= f.file_field :photo %>  
  9.   
  10.   <%= f.hidden_field :photo_cache %>  
  11.   
  12. </div>  


このままだと画像が表示されないので、画像表示用のメソッドを作る。
まずはapp/controllers/users_controller.rbに以下を追記

  1. require 'mongo'  
  2.   
  3. class UsersController < ApplicationController  
  4.   
  5.   def serve  
  6.   
  7.     gridfs_path = env["PATH_INFO"].gsub("/images/""")  
  8.   
  9.     begin  
  10.   
  11.       gridfs_file = Mongo::GridFileSystem.new(Mongoid.database).open(gridfs_path, 'r')  
  12.   
  13.       self.response_body = gridfs_file.read  
  14.   
  15.       self.content_type = gridfs_file.content_type  
  16.   
  17.     rescue  
  18.   
  19.       self.status = :file_not_found  
  20.   
  21.       self.content_type = 'text/plain'  
  22.   
  23.       self.response_body = ''  
  24.   
  25.     end  
  26.   
  27.   end  


config/routes.rbに以下を追記
match "/images/uploads/*path" => "users#serve"

これでアプリ側の設定は終了。git でcommitしておきます。
# git add .
# git commit -m 'first commit'

次にheroku側の設定をします。
#heroku create --stack cedar
#heroku addons:add mongolab:starter

あとはherokuにdeployするだけ
#git push heroku master

2012年2月11日土曜日

facebookアプリでsessionが維持できない

facebookでiframeを使ったアプリをつくろうとした時に、session内に情報を入れられない時がある。


protect_from_forgery :except => :index


そのときは、sessionを使いたいコントローラーだけ、上記のように除外する。