前書き
初心者向けに自分なりの言葉で柔らかく書いたつもりです。非エンジニアですが、メモ目的とアウトプットの恩恵のために記事にしました。間違っているところなどありましたら、コメントをください。
あとコードにコメントが多く可読性が悪いかもしれません。そこは申し訳なく…
iosからAWSまでの一連の流れを記事にしているためかなり長くなっているので、記事を分けて書いています。
- RailsとiOS(Swift)とAWS間をAPI通信で、データファイル保存させるまで①devise token auth編
- RailsとiOS(Swift)とAWS間をAPI通信で、データファイル保存させるまで②Alamofire、Swiftyjson編 devise_token_auth(認証)のデータの送受信
- RailsとiOS(Swift)とAWS間をAPI通信で、データファイル保存させるまで④carrierwave編
③までのものがこちら
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でブラウザに表示させる処理は、また別にコントローラーを作成してアクションで処理させるようにすると良い
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ファイルを新規作成して下記を記述
# コメント
# 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
# コメント
# json.partial! "api_posts/api_post", api_post: @api_post
json.extract! @api_post, :id, :user_id, :post_image, :title_name
# 送るjsonの設定
json.merge! @api_post.attributes
json.array! @api_posts, :id, :web_user_id, :post_image, :title_name
作成したコントローラーアクションのルートを定義
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する
import UIKit
import Alamofire
import SwiftyJSON
パラメータの保存先
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 = ""//プロパティ
}
ストーリーボードとの接続とパラメータ用の変数の用意
@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データを入れる変数
認証時に取ってきたトークンとユーザー情報の取得
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)")
}
文字列のデータの手入力を省くために、テキスト自動生成用の関数を用意
//ランダムに文字列を生成してくれる関数
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アクション
//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アクション
//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アクション
//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アクション
//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アクション
//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アクション
//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 {
//ログインしていないと警告を出す
}
}
carrierwave編
記事の初めにも記述しましたが、iosからAWSまでの一連の流れを記事にしているためかなり長くなってしまったので他の記事でも紹介しています。
こちらを続けてご覧下さい。