『RailsとiPhoneではじめるアプリケーション開発』を進める時の注意点・ハマったこと Chapter 12

いよいよ最終章!
やっとこの苦しみから解放される!

気を引き締めて行きます!

RailsとiPhoneではじめるアプリケーション開発

RailsとiPhoneではじめるアプリケーション開発

Chapter 01 -> 03
『RailsとiPhoneではじめるアプリケーション開発』を進める時の注意点・ハマったこと Chapter 03まで - diceK66のブログ
Chapter 04 -> 06
『RailsとiPhoneではじめるアプリケーション開発』を進める時の注意点・ハマったこと Chapter 06まで - diceK66のブログ
Chapter 07
『RailsとiPhoneではじめるアプリケーション開発』を進める時の注意点・ハマったこと Chapter 07 - diceK66のブログ
Chapter 08 -> 09
『RailsとiPhoneではじめるアプリケーション開発』を進める時の注意点・ハマったこと Chapter 08 -> 09 - diceK66のブログ
:token_authenticatableが削除されたので他の方法で実装してみた
『RailsとiPhoneではじめるアプリケーション開発』を進める時の注意点・ハマったこと Chapter 010 - diceK66のブログ
Chapter 10
:token_authenticatableが削除されたので他の方法で実装してみた - diceK66のブログ
Chapter 11
『RailsとiPhoneではじめるアプリケーション開発』を進める時の注意点・ハマったこと Chapter 011 - diceK66のブログ

重要事項

エラーが起きたらコンソールをじっくり読みましょう

特に自分が作成or編集したファイルがログに出てきたら、必ず正しくソースが書けているか等をチェックしましょう

配布されているサンプルソースは動きませんが、たまに役にたちます

http://www.impressjapan.jp/support/aftercare/download/rails-iphone-sample.zip
なお、このChapter辺りから、サンプルソースをコピペで作っています。
本の細かいミスは見落としている可能性があります。
逆に、本でわからない事があったら、サンプルソースを見たらわかる事もあるかもしれません。

sidekeqの起動方法

本の通りにsidekiqを起動するとエラーになります。
sidekiqを起動する前に、redisを起動してください。
(別ウィンドウで起動する必要があります、Railsとあわせると3つウィンドウを開く事になります)

$ redis-server

sidekiqを起動できたら、railsサーバーも再起動してください。

ここまでで、終わりにします。

疲れたというのもありますが、この先を見て行くと、以下のような感じになっています。

この辺りの内容は人気なので、この本と無駄な戦いをしなくとも、ネットでしっかりした情報がいくらでも手に入りそうですし、自分もある程度は知っている内容です。

まだこの本で扱うような領域の開発は続けて行くつもりなので、もう少し知識が付いて、暇な時に改めて進めようかと思います。
その時にはまたブログを書きます。

『RailsとiPhoneではじめるアプリケーション開発』を進める時の注意点・ハマったこと Chapter 011

それでは気を改めて引き続き『RailsiPhoneではじめるアプリケーション開発』を進めます。
ちなみに、さっき食べた夕食をなぜか吐いてしまいましたが、それはきっとこの本によるストレスのせいでは無いと信じたいですw


RailsとiPhoneではじめるアプリケーション開発

RailsとiPhoneではじめるアプリケーション開発

Chapter 01 -> 03
『RailsとiPhoneではじめるアプリケーション開発』を進める時の注意点・ハマったこと Chapter 03まで - diceK66のブログ
Chapter 04 -> 06
『RailsとiPhoneではじめるアプリケーション開発』を進める時の注意点・ハマったこと Chapter 06まで - diceK66のブログ
Chapter 07
『RailsとiPhoneではじめるアプリケーション開発』を進める時の注意点・ハマったこと Chapter 07 - diceK66のブログ
Chapter 08 -> 09
『RailsとiPhoneではじめるアプリケーション開発』を進める時の注意点・ハマったこと Chapter 08 -> 09 - diceK66のブログ
:token_authenticatableが削除されたので他の方法で実装してみた
『RailsとiPhoneではじめるアプリケーション開発』を進める時の注意点・ハマったこと Chapter 010 - diceK66のブログ
Chapter 10
:token_authenticatableが削除されたので他の方法で実装してみた - diceK66のブログ

重要事項

エラーが起きたらコンソールをじっくり読みましょう

特に自分が作成or編集したファイルがログに出てきたら、必ず正しくソースが書けているか等をチェックしましょう

配布されているサンプルソースは動きませんが、たまに役にたちます

http://www.impressjapan.jp/support/aftercare/download/rails-iphone-sample.zip
なお、このChapter辺りから、サンプルソースをコピペで作っています。
本の細かいミスは見落としている可能性があります。
逆に、本でわからない事があったら、サンプルソースを見たらわかる事もあるかもしれません。

p.259カオスのはじまり・・・いや既にカオスだったけど・・・

もはや本文で何を説明しようとしているのか意味が分かりません。
素直に配布されているサンプルソースをコピった方がよいでしょう。
ちなみに、TPTravelAddControllerというのは、TPTravelCreateControllerの間違いでしょう。
やっと「TP」が付いたのに惜しい!

で、やたらいろんなところに同じようなサーバーとの通信処理が書かれていますが、バグの温床になるので、どこかにまとめた方がよいでしょう。
そういうキレイなソースの書き方的なものはこの本に求めるのは止めた方が良さそうです。

BlocksKitは1.8.2を指定しましょう

おそらく、こんなエラーが発生すると思います。

No visible @interface for 'UIBarButtonItem' declares the selector 'initWithBarButtonSystemItem:handler:'

どうもBlocksKitのバージョンがあっていないのが原因のようです。
一度Xcodeを閉じ、Podfileを編集して、再度pod installすると解消します。

pod 'BlocksKit', '1.8.2'

auth_tokenをRailsに渡す

:token_authenticatableの代替策を実装したのでそれにあわせて、auth_tokenを渡す必要がありました(私の場合)。

「MR_」を付けましょう

MagicalRecordを使用しているので、contextにアクセスする時はMR_XXXじゃないのかな?

参考までに、auth_tokenとMR_の件を最低限修正したソースは以下の通りです。
(まだ問題が潜んでいるかもしれませんが・・・)

- (void)getData
{
    
    
    TravelPhotoAPI *tp_api = [TravelPhotoAPI sharedInstance];
    TPUserInfo *user_info = [TPUserInfo sharedInstance];
    AFHTTPClient *sharedClient = [TravelPhotoAPI sharedClient];
    NSString *url = [tp_api travelPath];
    
    [sharedClient setParameterEncoding:AFFormURLParameterEncoding];
    NSMutableURLRequest *request = [sharedClient requestWithMethod:@"GET"
                                                              path: url
                                                        parameters:@{@"user[email]" :[user_info loadEmail],
                                    @"user[auth_token]" : [user_info loadAuthToken]}
                                    ];
    
    AFJSONRequestOperation *operation = [AFJSONRequestOperation
                                         JSONRequestOperationWithRequest:request
                                         success:^(NSURLRequest *request, NSHTTPURLResponse *response,id JSON) {
                                             
                                             
                                             NSArray *travels = [JSON valueForKeyPath:@"travels"];
                                             NSDictionary *user = [JSON valueForKeyPath:@"user"];
                                             
                                             NSManagedObjectContext *context = [NSManagedObjectContext MR_defaultContext];
                                             
                                             
                                             User *user_model = [User MR_findFirstByAttribute:@"id" withValue:[user valueForKey:@"id"]];
                                             
                                             if(user_model == NULL){
                                                 user_model = [User MR_createEntity];
                                             }
                                             
                                             user_model.id = [user valueForKey:@"id"];
                                             user_model.username = [user valueForKey:@"username"];
                                             
                                             [context MR_saveToPersistentStoreAndWait];
                                             
                                             
                                             NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
                                             [formatter setDateFormat:@"yyyy-MM-dd"];
                                             
                                             for (NSDictionary * travel in travels) {
                                                 Travel *travel_model = [Travel MR_findFirstByAttribute:@"id" withValue:[travel valueForKey:@"id"]];
                                                 if(travel_model == NULL){
                                                     travel_model = [Travel MR_createEntity];
                                                 }
                                                 // NSLog(@"%@",travel);
                                                 travel_model.id = [travel valueForKey:@"id"];
                                                 travel_model.title = [travel valueForKey:@"title"];
                                                 travel_model.startdate = [formatter dateFromString:[travel valueForKey:@"startdate"]];
                                                 travel_model.enddate = [formatter dateFromString:[travel valueForKey:@"enddate"]];
                                                 travel_model.user = user_model;
                                                 if ([[travel valueForKey:@"photo_url"] isEqual:[NSNull null]]){
                                                     travel_model.photo_url = @"";
                                                     
                                                 }
                                                 else{
                                                     travel_model.photo_url = [travel valueForKey:@"photo_url"];
                                                     
                                                 }
                                                 // NSLog(@"%@",travel_model);
                                                 [context MR_saveToPersistentStoreAndWait];
                                                 
                                             }
                                             
                                             _travels = [Travel MR_findAllSortedBy:@"id" ascending:NO];
                                             [self.tableView reloadData];
                                             
                                             
                                             
                                         } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
                                             NSLog(@"Error: %@", error);
                                             
                                             [SVProgressHUD showErrorWithStatus:@"エラーが発生しました"];
                                             
                                         }];
    [sharedClient enqueueHTTPRequestOperation:operation];
    
}

にしても、カオスなソースだなぁ。

p.278 clearUserInfoメソッドにも「MR_」が必要では?

こんな感じにしないとエラーになると思います。

- (void)clearUserInfo{
    [self saveEmail:nil];
    [self savePassword:nil];
    [self saveAuthToken:nil];
    
    NSManagedObjectContext *context = [NSManagedObjectContext MR_defaultContext];;
    
    [Travel MR_truncateAll];
    [User MR_truncateAll];
    [context MR_saveToPersistentStoreAndWait];
}

旅の登録処理がごっそり抜けてる!

ここで旅モデルに関する実装が終わったかのように見えますが、まだ肝心な旅の登録処理が実装されていません。
相変わらず、まさにこの時期入社してくる新入社員が書くようなカオスなソースですが、サンプルソースを修正して、最低限動くようにしたコードを記載します。
>|objc|

  • (void)onSubmit{



[SVProgressHUD showWithStatus:@"送信中" maskType:SVProgressHUDMaskTypeBlack];
QEntryElement *title = (QEntryElement *)[self.root elementWithKey:@"title"];
QDateTimeInlineElement *startdate = (QDateTimeInlineElement *)[self.root elementWithKey:@"startdate"];
QDateTimeInlineElement *enddate = (QDateTimeInlineElement *)[self.root elementWithKey:@"enddate"];

if(startdate.dateValue == NULL || enddate.dateValue == NULL ||title.textValue == NULL){
[SVProgressHUD showErrorWithStatus:@"フォームに値が入力されていません。"];
return;
}

if([startdate.dateValue compare:enddate.dateValue] == NSOrderedDescending){
[SVProgressHUD showErrorWithStatus:@"日付が不正です"];
return;

}

//通信処理
TravelPhotoAPI *tp_api = [TravelPhotoAPI sharedInstance];
TPUserInfo *user_info = [TPUserInfo sharedInstance];

if([tp_api.networkStatus boolValue] == NO){
return;
}

AFHTTPClient *sharedClient = [TravelPhotoAPI sharedClient];

NSDictionary *userDic = @{@"travel[title]": title.textValue ,
@"travel[startdate]": startdate.dateValue,
@"travel[enddate]": enddate.dateValue,
@"user[email]" :[user_info loadEmail],
@"user[auth_token]" : [user_info loadAuthToken]
};

NSString *path = [tp_api travelCreatePath];
NSLog(@"%@",path);
[sharedClient setParameterEncoding:AFFormURLParameterEncoding];
AFJSONRequestOperation *operation = [AFJSONRequestOperation

JSONRequestOperationWithRequest:[sharedClient requestWithMethod:@"POST"
path: path
parameters: userDic

]

success:^(NSURLRequest *request, NSHTTPURLResponse *response,id JSON) {

[[NSNotificationCenter defaultCenter] postNotificationName:TPReloadTravels object:self userInfo:nil];


[self.navigationController popToRootViewControllerAnimated:TRUE];

[SVProgressHUD showSuccessWithStatus:@"作成が完了しました"];


} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(@"Error: %@", error);
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:@"エラーが発生しました" message:@"メールアドレスまたはパスワードが間違っています"
delegate:self cancelButtonTitle:@"確認" otherButtonTitles:nil];

[alertView show];


[SVProgressHUD dismiss];


}];
[sharedClient enqueueHTTPRequestOperation:operation];
}
|

You can only send 0 photo at a time?

p.287に『きちんとアップロードされているようです。』とありますので、一応このタイミングでテストしてみましたが、当然のごとくできません。
私の場合は、シミュレーターで写真を選択しようとすると、「You can only send 0 photo at a time」と言われ、写真を1枚も選択できませんでした。
これも正直原因がよくわかりませんでしたが、本と同じバージョンにあわせる事で解決しました。

//Podfile
pod 'ELCImagePickerController', '0.1.1'

p.291TravelPhotoControllerがない

p.294で実装しますので、一旦エラーのまま進めます。

『PaperclipでS3に画像保存』はやりませんでした。

ただし、S3は個人的に使っているので、機会があれば何か参考になりそうな情報を提供したいと思います。

Chapter 11で残ってしまった問題

実は旅の写真をうまく表示する事ができず、困っています。
まず、最初の旅リストに表示されるリストのサムネイルは、RubyがRootからのURLしか返していないので、http://localhost:3000から返すように修正したところ、表示されるようになりました。
f:id:diceK66:20140402191924p:plain
ところが、そこで旅を選んだ時の写真一覧が何故か表示されず、苦戦中です。
f:id:diceK66:20140402192011p:plain
真っ白に見えますが、実はセル自体は生成されているので、タップすると拡大して表示する事ができます。
(カバー写真にも登録できます)
f:id:diceK66:20140402192040p:plain

おそらくイージーミスだとは思うのですが、一旦あきらめて、最終章に進もうと思います。

所感

オブジェクト分け、メソッドの分割、プロジェクトの体系等、言い出したら切りがないほど改善すべき点はありますが、実際の現場ではこのようなカオスなプログラムと戦う事ってよくあります。
そういう意味ではとても「実践的」な本だとは思います。
ただ、一つだけ言えるのは、これだけトラブルと戦って、いろいろな事を調べながら進めていると、かなり実力が付いているのが実感できますw

:token_authenticatableが削除されたので他の方法で実装してみた

4月1日ですが、エイプリルフールエントリーではないので、ご安心を!
桜がきれいに咲いて、『RailsiPhoneではじめるアプリケーション開発』で疲れた私の心を癒してくれましたw

今回は、こちらのエントリの補足です。
『RailsとiPhoneではじめるアプリケーション開発』を進める時の注意点・ハマったこと Chapter 010 - diceK66のブログ

タイトル通り、:token_authenticatableが新しいdeviseでは削除されているので、他の方法で実装してみました。

Userテーブルにauthentication_tokenカラムを追加

$ rails g migration AddAuthenticationTokenToUser authentication_token:string

migrationファイルを編集

追加したauthentication_tokenカラムにユニークインデックスをはっておきます。

class AddAuthenticationTokenToUser < ActiveRecord::Migration
  def change
    add_column :users, :authentication_token, :string
    add_index :users, :authentication_token, unique: true #ここを追記
  end
end

DBに反映

いつものコマンドでDBに反映します。

$ rake db:migrate

application_controller.rbを修正

おそらく、現時点ではほぼ空になっているはずですが、以下のようにコードを追記します。
翻訳は適当ですのでご勘弁をw

class ApplicationController < ActionController::Base
  protect_from_forgery

  # これはDeviseのauthenticate_user!メソッドが呼ばれる前に呼び出される独自メソッドです。
  before_filter :authenticate_user_from_token!
  # こちらがDeviseのメソッドです
  before_filter :authenticate_user!
 
  private
  def authenticate_user_from_token!
    user_email = params[:user][:email].presence
    user       = user_email && User.find_by_email(user_email)
 
    # タイミング攻撃を防ぎつつ、paramsのトークンとDBのトークンを比較します
    if user && Devise.secure_compare(user.authentication_token, params[:user][:auth_token])
      sign_in user, store: false
    end
  end
end

userモデルにメソッドを追記

これだけだとカラムが追加されているだけなので、models/user.rb必要なメソッドを追加します。

(略)
  def reset_authentication_token!
    self.authentication_token = generate_authentication_token
    save
  end

  private
  def generate_authentication_token
    loop do
      token = Devise.friendly_token
      break token unless User.where(authentication_token: token).first
    end
  end
end

以上で、正常にログインできるようになったと思います。

たまには桜でも見てリフレッシュするといい事ありますね!