Simple gadget life programming diary

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

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

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

プログラム上でStoryboardのViewControllerを呼び出す。

Storyboardの名前はファイル名。
デフォルトだと「Main_iPhone.storyboard」となっていて、拡張子より前の部分が名前となる。

呼び出すViewControllerはStoryBoardの定義でStoryBorad IDが定義されていることが必須。

f:id:jtaka1012:20140405152952j:plain

コードは以下の通り。
ちなみにモーダルで新たにStoryBoard上のViewControllerを呼び出しています。

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main_iPhone" bundle:nil];
UINavigationController* nav = [[UINavigationController alloc] initWithRootViewController:[storyboard instantiateViewControllerWithIdentifier:@"categoryInputViewController"]];
    
[self presentViewController:nav animated:YES completion: nil];

Rubyでファイルのコピーと拡張子の変更を行うプログラムを書いてみた。

あまり綺麗なやり方では無いと思いますが、こうすれば出来ますよというサンプルということで載せておきます。(バージョンは2.0.0)

ディレクトリを削除するにはディレクトリ内が空である必要がるので、先にファイルの削除をした上でディレクトリを削除。
その後コピー元からディレクトリごとコピーを行う。
拡張子の変更では正規表現を用いて変更。大文字、小文字関係なく変換するようにしています。

# -*- coding: utf-8 -*-

require "find" 
require "FileUtils" 

orgDir = "" #コピー元のディレクトリパスを指定
newDir = "" #コピー先のディレクトリパスを指定

#元ディレクトリ内のファイルとディレクトリを削除する
#ファイルの削除
Find.find(newDir){ |item| 
  if File::ftype(item) == 'file' 
    File.delete(item) 
  end 
} 
#ディレクトリの削除
Find.find(newDir){ |item| 
  if File::ftype(item) == 'directory' 
    FileUtils.rmdir(item) 
  end 
} 

#コピーの実施
FileUtils.copy_entry(orgDir,newDir); 

#拡張子の変更処理
#ここでは.txtファイルを.rb拡張子に変更
Find.find(newDir){ |item| 
  if File::ftype(item) == 'file' 
    if item =~ /.*\.txt/i 
      orgFile = item 
      newFile = orgFile.sub(/\.txt/i, ".rb") 
      File.rename(orgFile, newFile) 
    end 
  end 
}

一応githubにもあげておきます。