LoginSignup
3
2

More than 5 years have passed since last update.

RailsとiOS(Swift)とAWS間をAPIモードで、データファイル保存呼び出しさせるまで③

Last updated at Posted at 2018-09-24

前書き

初心者向けに自分なりの言葉で柔らかく書いたつもりです。非エンジニアですが、メモ目的とアウトプットの恩恵のために記事にしました。間違っているところなどありましたら、コメントをください。
あとコードにコメントが多く可読性が悪いかもしれません。そこは申し訳なく…

iosからAWSまでの一連の流れを記事にしているためかなり長くなっているので、記事を分けて書いています。

③までのものがこちら
https://github.com/ToruMizuno/railswiftapi
④までのものがこちら
https://github.com/ToruMizuno/railswiftapi2

apiモードを使ってdevise_token_auth(認証)以外のデータの送受信(Rails側)

認証用のUser以外のモデルの作成

Postをscaffoldで作成

$ rails generate scaffold api/post

scaffoldで作成されたControllerの編集

全て外部からRails、Railsから外部への通信のためのコントローラーアクション
RailsのViewでブラウザに表示させる処理は、また別にコントローラーを作成してアクションで処理させるようにすると良い

app/controllers/api/posts_controller.rb

class Api::PostsController < ApplicationController
  # コメント
  # before_action :set_api_post, only: [:show, :update, :destroy]
  # 追加
  before_action :set_api_post, only: [:update, :destroy]
  before_action :authenticate_api_user!# 認証制限

  # GET /api/posts
  # GET /api/posts.json
  def index
    # 全てのPostを取ってくる
    @api_posts = Api::Post.all
    # 追加
    render 'index', :formats => [:json], :handlers => [:jbuilder]
  end

  # GET /api/posts/1
  # GET /api/posts/1.json
  def show
    # 追加
    # Postのidで検索
    @api_post = Api::Post.find_by(id: params[:id])
    render 'show', :formats => [:json], :handlers => [:jbuilder]
  end

  # createは書き換えて使用する
  # POST /api/posts
  # POST /api/posts.json
  def create
    # @api_post = Api::Post.new(api_post_params)
    #
    # if @api_post.save
    #   render :show, status: :created, location: @api_post
    # else
    #   render json: @api_post.errors, status: :unprocessable_entity
    # end

    @api_post = Api::Post.new(create_params)

    begin
      Api::Post.transaction do
        @api_post.save!# save!で例外を出力する
      end
      # 正常に動作した場合の処理

    rescue ActiveRecord::RecordInvalid => e
      # 例外が発生した場合の処理
      @api_post = e.record
     end
  end

  # updateはupdate_web_post_idを使うので必要ないかも?
  # PATCH/PUT /api/posts/1
  # PATCH/PUT /api/posts/1.json
  def update
    if @api_post.update(api_post_params)
      render :show, status: :ok, location: @api_post
    else
      render json: @api_post.errors, status: :unprocessable_entity
    end
  end

  # destroyはそのまま使用する
  # DELETE /api/posts/1
  # DELETE /api/posts/1.json
  def destroy
    @api_post.destroy
  end

  # updateはこれを使う
  # 追加
  def update_post_id
    # post_idで1つのレコードを検索して取って来てもらう
    post = Api::Post.find_by(id: params[:id])
    post.update_attributes(update_params)
  end

  # 追加
  # web_user_idを使って自分のpostのみを、全て取得する
  def my_post_show_web_user_id
    # webuseridで引っかかる全てのレコードを検索して取って来てもらうようにする
    @api_posts = Api::Post.where(web_user_id: params[:id])
  end

  private

    # 冒頭のbefore_actionで使用する
    # Use callbacks to share common setup or constraints between actions.
    def set_api_post
      @api_post = Api::Post.find(params[:id])
    end

    # 標準のupdateであるが、必要でないかも?
    # Never trust parameters from the scary internet, only allow the white list through.
    def api_post_params
      params.fetch(:api_post, {})
    end



    # createで使用する
    # 追加
    # postを新規作成した場合、これだけのカラムを作ってくれる
    def create_params
      params.permit(
        :web_user_id,
        :post_image,# 画像しか作成しない。画像を作成と共に他のデータを送信しないから。更新でデータ保存する
        :title_name
      )
    end

    # update_post_idで使用する
    # 追加
    # postを更新する場合、これだけのカラムを更新する
    def update_params
      params.permit(
        :web_user_id,
        :post_image,
        :title_name
       )
    end

end

scaffoldで出来たマイグレーションファイルを編集

class CreateApiPosts < ActiveRecord::Migration[5.1]
  def change
    create_table :api_posts do |t|

      t.integer  :web_user_id
      t.string   :post_image# 画像保存はstring
      t.string  :title_name

      t.timestamps
    end
  end
end

テーブル作成

$ rake db:migrate

コントローラーアクションに対応したjbuilderを作成して編集

******.json.jbuilderファイルを新規作成して下記を記述

app/views/api/posts/index.json.jbuilder
# コメント
# json.array! @api_posts, partial: 'api_posts/api_post', as: :api_post
json.set! :posts do
  json.array! @api_posts, :id, :user_id, :post_image, :title_name
end
app/views/api/posts/show.json.jbuilder
# コメント
# json.partial! "api_posts/api_post", api_post: @api_post
json.extract! @api_post, :id, :user_id, :post_image, :title_name
app/views/api/posts/create.json.jbuilder
# 送るjsonの設定
json.merge! @api_post.attributes
app/views/api/posts/my_post_show_web_user_id.json.jbuilder
json.array! @api_posts, :id, :web_user_id, :post_image, :title_name

作成したコントローラーアクションのルートを定義

config/routes.rb
Rails.application.routes.draw do
  # コメント
  # namespace :api do
  #   resources :posts
  # end

  # namespaceは標準のルートの/の間にフォルダー名を入れられる
  namespace :api, default: {format: :json} do
    # resources :posts, only: :create# createのみのルートを作る
    resources :posts do# 7つのアクションのルートを作る
      member do
        get :my_post_show_web_user_id# 追加でid付き(web_user_id)のルートを作る
        put :update_post_id# 追加でid付き(web_user_id)のルートを作る
      end
    end
  end

  # コメント
  # mount_devise_token_auth_for 'User', at: 'auth'
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  namespace :api do
    mount_devise_token_auth_for 'User', at: 'auth', controllers: {
        registrations: 'api/auth/registrations'
    }
  end
end

apiモードを使ってdevise_token_auth(認証)以外のデータの送受信(iOS側)

PostViewController.swiftファイルを作成して記述していく

ライブラリーをimportする

PostViewController.swift
import UIKit
import Alamofire
import SwiftyJSON

パラメータの保存先

Post.swiftファイルを作成して記述する

model/Post.swift
import UIKit

class Post: NSObject {
    var id: Int = 0
    var web_user_id: Int = 0//所属しているUserのidか何か
    var postImage: String! = ""//プロパティ
    var titlename: String = ""//プロパティ
}

ストーリーボードとの接続とパラメータ用の変数の用意

PostViewController.swift

    @IBOutlet weak var idTextField: UITextField!

    var accesstoken: String!
    var client: String!
    var uid: String!
    var webuser: Websuer!


    var posts = [Post]()//全てのPostデータを入れる変数
    var myposts = [Post]()//自分のPostデータを入れる変数

認証時に取ってきたトークンとユーザー情報の取得

PostViewController.swift
override func viewDidLoad() {
        super.viewDidLoad()        

        //appDelegateからアクセス制限用のトークンと、通信したいuserの情報を取ってくる
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        webuser = appDelegate.webuser
        accesstoken = appDelegate.accesstoken
        client = appDelegate.client
        uid = appDelegate.uid
        print("\(webuser)")
        print("\(accesstoken)")
        print("\(client)")
        print("\(uid)")
   }

文字列のデータの手入力を省くために、テキスト自動生成用の関数を用意

PostViewController.swift
//ランダムに文字列を生成してくれる関数
    func generate(length: Int) -> String {
        let base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        var randomString: String = ""

        for _ in 0..<length {
            let randomValue = arc4random_uniform(UInt32(base.count))
            randomString += "\(base[base.index(base.startIndex, offsetBy: Int(randomValue))])"
        }
        return randomString
    }

ボタンでRails側のposts_controller.rbの各種アクションを発動

Railsの各種postのroutesにアクセスして、アクションを実行してもらう。
各種アクションで取得した値をjbuilderを通してレスポンスする。

indexアクション

PostViewController.swift
    //indexアクション
    //全てのpostを取ってくる
    @IBAction func indexPostButtonClicked(_ sender: Any) {

        //ログインやサインアップでこれらの情報を取ってこれていた場合は
        if self.accesstoken != nil && self.client != nil && self.uid != nil {

            //取り出したトークン情報をヘッダーに格納
            var headers: [String : String] = [:]
            headers["access-token"] = self.accesstoken
            headers["client"] = self.client
            headers["uid"] = self.uid

            print("accesstoken: \(String(describing: self.accesstoken))")
            print("client: \(String(describing: self.client))")
            print("uid: \(String(describing: self.uid))")

            //GET /api/posts(.:format)
            Alamofire.request("http://localhost:3000/api/posts", method: .get, headers: headers).responseJSON { response in
                print("Request: \(String(describing: response.request))")
                print("Response: \(String(describing: response.response))")
                print("Error: \(String(describing: response.error))")

                //アクセスできた場合
                if response.response?.statusCode == 200 {
                    print("通信に成功しました")

                    print("Request: \(String(describing: response.request))")
                    print("Response: \(String(describing: response.response))")
                    print("Error: \(String(describing: response.error))")

                    //エラーがなかった場合
                    if response.error == nil {

                        self.posts.removeAll()

                        //responseから保存したデータのIDを抜き出す
                        let json:JSON = JSON(response.result.value ?? kill)

                        json.forEach { (arg) in
                            let (_, json) = arg

                            //Postに保存
                            let post = Post()

                            //パラメーターセット
                            post.id = json["id"].intValue
                            post.web_user_id = json["web_user_id"].intValue
                            post.titlename = json["title_name"].stringValue

                            print("id: \(String(describing: post.id))")
                            print("web_user_id: \(String(describing: post.web_user_id))")
                            print("title_name: \(String(describing: post.titlename))")

                            self.posts.append(post)
                        }

                    }

                    //アクセスできなかった場合
                } else {

                    print("アクセスできませんでした")

                }

            }

            //ヘッダー情報を取ってこれていなかった場合
        } else {

            //ログインしていないと警告を出す

        }

    }

showアクション

PostViewController.swift
    //showアクション
    //postのidで取ってくる
    @IBAction func showPostButtonClicked(_ sender: Any) {

        //ログインやサインアップでこれらの情報を取ってこれていた場合は
        if self.accesstoken != nil && self.client != nil && self.uid != nil {

            let postid: Int = Int(self.idTextField.text!)!//TextFieldにIDを入力しないとエラーになるよ
            let accesstoken: String = self.accesstoken
            let client: String = self.client
            let uid: String = self.uid

            print("postid: \(String(describing: postid))")
            print("accesstoken: \(String(describing: accesstoken))")
            print("client: \(String(describing: client))")
            print("uid: \(String(describing: uid))")

            //取り出したトークン情報をヘッダーに格納
            var headers: [String : String] = [:]
            headers["access-token"] = accesstoken
            headers["client"] = client
            headers["uid"] = uid

            print("requestURL_post: http://localhost:3000/api/posts/\(postid)")

            //GET /api/posts/:id(.:format)
            //ログインした際にとって来たトークン情報と、自分のwebuserid(アカウントID)を使って自分のpostのみを取ってくる
            Alamofire.request("http://localhost:3000/api/posts/\(postid)", method: .get, encoding: JSONEncoding.default, headers: headers).responseJSON { response in

                //アクセスできた場合
                if response.response?.statusCode == 200 {

                    print("Request: \(String(describing: response.request))")
                    print("Response: \(String(describing: response.response))")
                    print("Error: \(String(describing: response.error))")
                    print("Value: \(String(describing: response.result.value))")

                    //responseから保存したデータのIDを抜き出す
                    let json:JSON = JSON(response.result.value ?? kill)
                    print("json: \(String(describing: json))")

                    //Postに保存
                    let post = Post()

                    //パラメーターセット
                    post.id = json["id"].intValue
                    post.web_user_id = json["web_user_id"].intValue
                    post.titlename = json["title_name"].stringValue

                    print("id: \(String(describing: post.id))")
                    print("web_user_id: \(String(describing: post.web_user_id))")
                    print("title_name: \(String(describing: post.titlename))")

                    //アクセスできなかった場合は再度取ってくるように通信する
                } else {

                    //アクセスできなかった場合は再度取ってくるように通信する

                    print("Error: \(String(describing: response.error))")
                }

            }

            //ヘッダー情報を取ってこれていなかった場合
        } else {

            //ログインしていないと警告を出す

        }


    }

createアクション

PostViewController.swift
    //createアクション
    //普通に保存する
    @IBAction func createPostButtonClicked(_ sender: Any) {

        let rndstr = generate(length: 32)

        //パラメーターを辞書で入れていく//左辺がカラム名、右辺が値
        var params: [String: Any] = [:]
        params["web_user_id"] = webuser.web_user_id
        params["title_name"] = rndstr

        //ログインやサインアップでこれらの情報を取ってこれていた場合は
        if self.accesstoken != nil && self.client != nil && self.uid != nil {

            //取り出したトークン情報をヘッダーに格納
            var headers: [String : String] = [:]
            headers["access-token"] = self.accesstoken
            headers["client"] = self.client
            headers["uid"] = self.uid

            print("accesstoken: \(String(describing: self.accesstoken))")
            print("client: \(String(describing: self.client))")
            print("uid: \(String(describing: self.uid))")

            //POST /api/posts(.:format)
            Alamofire.request("http://localhost:3000/api/posts", method: .post, parameters: params, headers: headers).responseJSON { response in
                print("Request: \(String(describing: response.request))")
                print("Response: \(String(describing: response.response))")
                print("Error: \(String(describing: response.error))")

                //アクセスできた場合
                if response.response?.statusCode == 200 {
                    print("通信に成功しました")

                    print("Request: \(String(describing: response.request))")
                    print("Response: \(String(describing: response.response))")
                    print("Error: \(String(describing: response.error))")

                    //エラーがなかった場合
                    if response.error == nil {

                        //Postに保存
                        //保存後にjsonが返って来ないのでそのまま保存前の値を代入する
                        let post = Post()

                        post.web_user_id = self.webuser.web_user_id
                        post.titlename = rndstr

                        print("web_user_id: \(String(describing: post.web_user_id))")
                        print("title_name: \(String(describing: post.titlename))")
                    }

                    //アクセスできなかった場合
                } else {

                    print("アクセスできませんでした")

                }

            }

            //ヘッダー情報を取ってこれていなかった場合
        } else {

            //ログインしていないと警告を出す

        }

    }

updateアクション

PostViewController.swift
    //updateアクション
    //post_idで更新する
    @IBAction func updatePostButtonClicked(_ sender: Any) {

        let rndstr = generate(length: 32)

        //パラメーターを辞書で入れていく//左辺がカラム名、右辺が値
        var params: [String: Any] = [:]
        //params["post_image"] =
        params["title_name"] = rndstr

        //ログインやサインアップでこれらの情報を取ってこれていた場合は
        if self.accesstoken != nil && self.client != nil && self.uid != nil {

            let postid: Int = Int(idTextField.text!)!

            //取り出したトークン情報をヘッダーに格納
            var headers: [String : String] = [:]
            headers["access-token"] = self.accesstoken
            headers["client"] = self.client
            headers["uid"] = self.uid

            print("accesstoken: \(String(describing: self.accesstoken))")
            print("client: \(String(describing: self.client))")
            print("uid: \(String(describing: self.uid))")

            //PUT /api/posts/:id/update_post_id(.:format)
            Alamofire.request("http://localhost:3000/api/posts/\(postid)/update_post_id", method: .put, parameters: params, headers: headers).responseJSON { response in
                print("Request: \(String(describing: response.request))")
                print("Response: \(String(describing: response.response))")
                print("Error: \(String(describing: response.error))")

                //型を指定した定数にいれないと比較演算子が使えない
                let res: Int = (response.response?.statusCode)!

                //アクセスできた場合(更新ができた場合)
                if res >= 200 && res < 300 {
                    print("通信に成功しました")

                    print("Request: \(String(describing: response.request))")
                    print("Response: \(String(describing: response.response))")
                    print("Error: \(String(describing: response.error))")

                    //アクセスできなかった場合
                } else {

                    print("アクセスできませんでした")

                }

            }

            //ヘッダー情報を取ってこれていなかった場合
        } else {

            //ログインしていないと警告を出す

        }

    }

destroyアクション

PostViewController.swift
    //destroyアクション
    //post_idで削除する
    @IBAction func destroyPostButtonClicked(_ sender: Any) {

        //ログインやサインアップでこれらの情報を取ってこれていた場合は
        if self.accesstoken != nil && self.client != nil && self.uid != nil {

            let postid: Int = Int(idTextField.text!)!

            //取り出したトークン情報をヘッダーに格納
            var headers: [String : String] = [:]
            headers["access-token"] = self.accesstoken
            headers["client"] = self.client
            headers["uid"] = self.uid

            print("accesstoken: \(String(describing: self.accesstoken))")
            print("client: \(String(describing: self.client))")
            print("uid: \(String(describing: self.uid))")

            //DELETE /api/posts/:id(.:format)
            Alamofire.request("http://localhost:3000/api/posts/\(postid)", method: .delete, headers: headers).responseJSON { response in
                print("Request: \(String(describing: response.request))")
                print("Response: \(String(describing: response.response))")
                print("Error: \(String(describing: response.error))")

                //型を指定した定数にいれないと比較演算子が使えない
                let res: Int = (response.response?.statusCode)!

                //アクセスできた場合(更新ができた場合)
                if res >= 200 && res < 300 {
                    print("通信に成功しました")

                    print("Request: \(String(describing: response.request))")
                    print("Response: \(String(describing: response.response))")
                    print("Error: \(String(describing: response.error))")

                    //アクセスできなかった場合
                } else {

                    print("アクセスできませんでした")

                }

            }

            //ヘッダー情報を取ってこれていなかった場合
        } else {

            //ログインしていないと警告を出す

        }

    }

my_post_show_web_user_idアクション

PostViewController.swift
    //my_post_show_web_user_idアクション
    //自分のpostのみ取ってくる
    @IBAction func myPostShowButtonClicked(_ sender: Any) {

        //ログインやサインアップでこれらの情報を取ってこれていた場合は
        if self.accesstoken != nil && self.client != nil && self.uid != nil {

            let webuserid: Int = self.webuser.web_user_id
            let accesstoken: String = self.accesstoken
            let client: String = self.client
            let uid: String = self.uid

            print("WebuserID: \(String(describing: webuserid))")
            print("accesstoken: \(String(describing: accesstoken))")
            print("client: \(String(describing: client))")
            print("uid: \(String(describing: uid))")

            //取り出したトークン情報をヘッダーに格納
            var headers: [String : String] = [:]
            headers["access-token"] = accesstoken
            headers["client"] = client
            headers["uid"] = uid

            print("requestURL_post: http://localhost:3000/api/posts/\(webuserid)/my_post_show_web_user_id.json")

            //GET /api/posts/:id/my_post_show_web_user_id(.:format)
            //ログインした際にとって来たトークン情報と、自分のwebuserid(アカウントID)を使って自分のpostのみを取ってくる
            Alamofire.request("http://localhost:3000/api/posts/\(webuserid)/my_post_show_web_user_id.json", method: .get, encoding: JSONEncoding.default, headers: headers).responseJSON { response in

                //アクセスできた場合
                if response.response?.statusCode == 200 {

                    print("Request: \(String(describing: response.request))")
                    print("Response: \(String(describing: response.response))")
                    print("Error: \(String(describing: response.error))")
                    print("Value: \(String(describing: response.result.value))")

                    self.myposts.removeAll()

                    //responseからとって来たデータを抜き出す
                    let json:JSON = JSON(response.result.value ?? kill)
                    json.forEach { (arg) in
                        let (_, json) = arg

                        //Postに保存
                        let post = Post()

                        //パラメーターセット
                        post.id = json["id"].intValue
                        post.web_user_id = json["web_user_id"].intValue
                        post.titlename = json["title_name"].stringValue

                        print("id: \(String(describing: post.id))")
                        print("web_user_id: \(String(describing: post.web_user_id))")
                        print("title_name: \(String(describing: post.titlename))")

                        self.myposts.append(post)

                    }

                    //アクセスできなかった場合
                } else {

                    //アクセスできなかった場合

                    print("Error: \(String(describing: response.error))")
                }

            }

            //ヘッダー情報を取ってこれていなかった場合
        } else {

            //ログインしていないと警告を出す

        }

    }

スクリーンショット 2018-09-18 20.31.24.png.png

carrierwave編

記事の初めにも記述しましたが、iosからAWSまでの一連の流れを記事にしているためかなり長くなってしまったので他の記事でも紹介しています。
こちらを続けてご覧下さい。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2