Simple gadget life programming diary

Simple gadget life の中の人によるプログラミングメモ

Twitter認証を要求する際、謎の401エラーとなるときの原因のひとつ

サーバの時刻が実際の時間とズレている。
VirtualBoxで開発している場合、立ち上げっぱなしでクライアントマシンをスリープしていると、エミュレートしているOSもスリープしてしまう。
その場合、エミュレートしているOSの時刻を正しく修正する必要がある。

CentOSは以下で修正可能。

sudo service ntpd stop
sudo ntpdate jp.pool.ntp.org
sudo service ntpd start

たまにハマる。忘れないようにメモ。

railsでgenerateで生成したあとで取り消すコマンド

[hoge@localhost app]$ rails g controller plan
      create  app/controllers/plan_controller.rb
      invoke  erb
      create    app/views/plan
      invoke  test_unit
      create    test/functional/plan_controller_test.rb
      invoke  helper
      create    app/helpers/plan_helper.rb
      invoke    test_unit
      create      test/unit/helpers/plan_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/plan.js.coffee
      invoke    scss
      create      app/assets/stylesheets/plan.css.scss

planのコントローラを生成したが、ホントはplansだった。
その場合、以下のコマンドで取り消しが可能。

[hoge@localhost controllers]$ rails destroy controller plan
      remove  app/controllers/plan_controller.rb
      invoke  erb
      remove    app/views/plan
      invoke  test_unit
      remove    test/functional/plan_controller_test.rb
      invoke  helper
      remove    app/helpers/plan_helper.rb
      invoke    test_unit
      remove      test/unit/helpers/plan_helper_test.rb
      invoke  assets
      invoke    coffee
      remove      app/assets/javascripts/plan.js.coffee
      invoke    scss
      remove      app/assets/stylesheets/plan.css.scss

destroyコマンドで生成を取り消すことが可能。

railsでhas manyオブジェクトをViewで編集する方法

ネストされたオブジェクトの中身を画面表示する。
has_manyで紐付いているオブジェクトの中身を表示する方法がわからなかったのでメモ。

モデルの構成は以下のとおり。

class Event < ActiveRecord::Base
  # attr_accessible :title, :body
  attr_accessible :title, :memo, :image_attributes, :sub_events_attributes
  
  has_one :image, class_name: "EventPicture", dependent: :destroy
  has_many :sub_events , dependent: :destroy

  accepts_nested_attributes_for :image, allow_destroy: true
  accepts_nested_attributes_for :sub_events, allow_destroy: true
end


class SubEvent < ActiveRecord::Base
  # attr_accessible :title, :body
  attr_accessible :event, :time, :content, :index

  belongs_to :event
end

eventオブジェクトの下にはhas_manyで複数のsub_eventが紐付いている。

これをViewで表示するためにはfields_forメソッドを使用する。
View側の処理は以下の通り。

<% @page_title = "イベントの編集" %>

<h1><%= @page_title %></h1>
<p><%= link_to "イベントに戻る" , @event %></p>

<%= form_for @event, html: {multipart: true} do |form| %>
<%= render "form", form: form %>
<p><%= form.submit %></p>
<% end %>

実際はformのヘルパで処理を実施。

<% @page_title = "新規・編集をするよ" %>

<h1><%= form.label :title, "タイトル" %></h1>
<h1><%= form.text_field :title %></h1>

<!-- #mergeはハッシュの統合。optionが不要な場合は{}(からの配列)を設定させる。 -->

<%= form.fields_for :image do |imgf| %>
  画像のアップロード<br>
  <%= imgf.file_field :uploaded_image %>
  <% if !imgf.object.new_record? && imgf.object.errors.empty? %>
    <%= event_image_tag @event, width: 600 %>
    <%= imgf.check_box :_destroy %>
    <%= imgf.label "削除する" %>
    <%= imgf.hidden_field :id %>
  <% end %>
<% end %>

<table class= "subevent">
  <tr>
    <th><%= form.label :time, "所要時間" %></th>
    <th>内容</th>
  </tr>
  <%= form.fields_for :sub_events, form.object.sub_events do |sevf| %>
    <% if sevf.object.errors.empty? %>
      <tr>
        <td><%= sevf.text_field :time %></td>
        <td><%= sevf.text_field :content %></td>
        <%= sevf.hidden_field :index %>
      </tr>
    <% end %>
  <% end %>
</table>

<%= form.label :memo, "メモ" %>
<%= form.text_field :memo %>

<%= form.fields_for :sub_events, form.object.sub_events do |sevf| %>
この部分でevent配下のsubeventオブジェクトを1つずつ取得しsevfへ代入している。
(表示するカラムと受け渡すオブジェクトを指定)
その後はオブジェクトが1つの場合と同様に処理を記載していく。

App Extensionでアプリ間連携をした際、host appの挙動調査

こちらのサンプルを使用させていただきました。

[iOS 8] App Extension #4 – Action Extension | Developers.IO

※今後追加していく予定。
・host側のアプリはバックグラウンドには入らずcontain appを呼び出す。
・contain appでは通信可能?
 →webViewは動作している。EvernoteやPocketでも通信しており、独自サーバへのアクセスも出来そう。
・contain appからの戻り値はhost側にブロックを使って定義。Arrayにobjectを入れて返却.

Ruby(Rails)にAPI作成支援ライブラリGrapeで任意の返却値を返す方法

サンプルを見ているとRailsのモデルをそのまま返すというサンプルが多く、モデルのなかでも必要な項目(idやタイムスタンプを除いて)を返すというサンプルがなかったのでメモ。

module TestApp
  class API < Grape::API
    prefix 'api'
    version 'v1', using: :path
    format :json
        
    resource :tweeturls do
      get do
        arrayUrl = Array.new(100)
        objs = Tweeturl.first(100)

        objs.each do |obj|
          arrayUrl.push({"url" => obj.url})
        end

        hashobj = Hash["urls" => arrayUrl]
        present hashobj
      end
    end
  end
end

モデルは以下

class CreateTweeturls < ActiveRecord::Migration
  def change
    create_table :tweeturls do |t|
      t.string :url
      t.timestamps
    end
  end
end

TwitterUrlsというモデルにはタイムスタンプとurl、自動で付与されるidを持っている。
そのモデルからurlだけを100個返却するのがAPIの内容。
自分でArrayやHashで組み立てて、presentで返却すれば作成完了。

iPhoneアプリでOAuth2.0認証からInstagramのAPIを叩いて情報を取得するまで。

InstagramなのでOAuthは2.0。
Webへのアクセスは有名ドコロのライブラリを使用。

使用するライブラリ
OAuth2 : OAuth2Client
HTTP通信 : AFNetworking
画像の非同期ダウンロード : SDWebImage

各ライブラリの詳細については以下の参考情報を参照。

参考情報

OAuth2
Selection 9: iOSでOAuth2認証を行う(feedlyクライアントの作成)

HTTP通信、画像の非同期ダウンロード
DeNAのiOSエンジニア内で利用頻度の高いライブラリをランキング化してみました #iOS #DeNA|CodeIQ MAGAZINE

InstagramAPI
Instagram APIの使い方まとめ(サンプルコード付)

プログラム起動前に実施すること

クライアントIDとクライアントシークレットを設定すること

AppDelegate.m

//clientId
static NSString * const kOauth2ClientClientId = @"xxx"; //クライアントIDを設定
//Client Secret
static NSString * const kOauth2ClientClientSecret = @"xxx"; //クライアントシークレットを設定

少しつまづいた所

OAuth2Client
クライアントIDやクライアントシークレットをセットするメソッドが参考情報と異なります。
keyChainGroupというトークンの情報をキーチェーンに保存するときに一意に識別するためのキー情報を設定する必要があります。ここの例では"hoge"としています。

AppDelegate.m

+ (void)initialize {
    
    NSString *authUrl = [kOauth2ClientBaseUrl stringByAppendingString:kOauth2ClientAuthUrl];
    NSString *tokenUrl = [kOauth2ClientBaseUrl stringByAppendingString:kOauth2ClientTokenUrl];
    
    [[NXOAuth2AccountStore sharedStore] setClientID:kOauth2ClientClientId
                                        secret:kOauth2ClientClientSecret
                                        scope:[NSSet setWithObjects:kOauth2ClientScopeUrl, nil]
                                        authorizationURL:[NSURL URLWithString:authUrl]
                                        tokenURL:[NSURL URLWithString:tokenUrl]
                                        redirectURL:[NSURL URLWithString:kOauth2ClientRedirectUrl]
                                        keyChainGroup:@"hoge" 
                                        forAccountType:kOauth2ClientAccountType];
    
}

AFNetworking
GETで情報を取得する際のパラメータへの設定方法。
NSDictionaryでパラメータ名をキーに、値をキーに対する情報として定義
作成した情報をアクセスメソッドのparametersにセットする

ViewController.m

- (void)p_getUserProfile:(NXOAuth2Account *)account {
    //get user profile on feedly user
    NSLog(@"account info : %@", account);
    NSLog(@"accessToken=%@",account.accessToken.accessToken);
    
    // AFNetworkingライブラリ使用箇所
    // AFHTTPRequestOperationManagerを利用して、InstagramからJSONデータを取得する
    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];

    //get profile
    NSDictionary *params = @{@"access_token":account.accessToken.accessToken};
    
    [manager GET:@"https://api.instagram.com/v1/users/27069716/"
      parameters:params
         success:^(AFHTTPRequestOperation *operation, id responseObject) {
             // 通信に成功した場合の処理
             NSLog(@"responseObject: %@", responseObject);
             NSString *url = responseObject[@"data"][@"profile_picture"];
             NSURL *imageURL = [NSURL URLWithString:url];
             
             // SDWebImage使用箇所
             UIImage *placeholderImage = [UIImage imageNamed:nil];
             [_imageView setImageWithURL:imageURL
                        placeholderImage:placeholderImage];
             
         }
         failure:^(AFHTTPRequestOperation *operation, NSError *error) {
             // エラーの場合はエラーの内容をコンソールに出力する
             NSLog(@"Error: %@", error);
         }];
}

初期画面でSign Inボタン押下でアクセストークン取得。
Get InformationでInstagramAPIを叩いて私のInstagramの情報を取得。
取得した情報からプロフィールの写真のアドレスを抜き出し、非同期で画像を取得。
エラーハンドリングは考慮できてないです。

作成したプログラムはgithubに置きました。
https://github.com/jtaka1012/OAuthTest

UIScrollViewで上に余分な隙間が出来てしまう時の回避方法

追記

こちらに解決方法を書きました。
UITableViewの上にヘッダー分の隙間が開くときの対処法 - Simple gadget life programming diary

SCrollViewのエリアをグレーに、貼り付けるViewを赤にして再現。
ちなみにグレーのエリアと赤いViewは同じ高さ。下のキャプチャでは赤いViewが埋まったようになっている。

f:id:jtaka1012:20140522134826j:plain

調べるとどうやら64pix分余分な隙間がある。
そのため以下のようにして回避。

    UIScrollView *_scrollView = [UIScrollView new];
    _scrollView.frame = CGRectMake(0, 164, 320, 320);
    _scrollView.pagingEnabled = NO;
    _scrollView.showsVerticalScrollIndicator = NO;
    _scrollView.showsHorizontalScrollIndicator = YES;
    _scrollView.scrollsToTop = NO;
    _scrollView.backgroundColor = [UIColor grayColor];
    _scrollView.delegate = self;

    //赤いView。y座標開始位置を-64に。
    UIView *kariview = [[UIView alloc]initWithFrame:CGRectMake(0, -64, 1320, 320)]; 
    kariview.backgroundColor = [UIColor redColor];
    [_scrollView addSubview:kariview];

    //ScrollViewのcontentSizeの高さも-64に。
    _scrollView.contentSize = CGSizeMake(kariview.bounds.size.width, kariview.bounds.size.height-64); 
    _scrollView.bounces = YES;

    [self.view addSubview:_scrollView];

これで実行すると、

f:id:jtaka1012:20140522135522j:plain

ちゃんと表示される。上下にも動かない。
本当はもっと正しい設定方法があるような気がする。。