てくてくテック

気ままに開発のメモを書いていこうと思います。主にSwiftかと。

Kituraのチュートリアル(ToDoBackend)で気になったことを調べた

はじめに

こんにちは。toosaaです。

前回、Kituraのチュートリアルの一つであるToDoBackendを動かしました。今回は、その中で気になった点について調べます。

Codable プロトコル

Swift4から追加されたプロトコルで、これを使うとjsonとの変換を勝手にやってくれます。すごく便利です参考。 今回のチュートリアルでは、Web APIとしてjsonを返すため利用しているのだと思います。そして、このcodableを使うためkituraのversionは swift4なのだと思います。

Equatable プロトコル

ToDoモデルはCodableプロトコル以外にEquatableプロトコルにも準拠しています。これはKituraとして必要というわけではなく、サンプルのupdateHandler()やdeleteHandler()でArrayの中からオブジェクトを見つけるのに必要だから利用しているようです。let idPosition = todoStore.index(of: idMatch)ここの部分です。

handlerの指定の仕方

チュートリアルだとApplication.swift内にhandlerのメソッドを実装してますが、実運用するにあたってはモデルに紐づけて実装したいです。これは単に、モデルclassにstaticメソッドで同じメソッドを実装すればいいです。とりあえず、データを格納するarray等もstaticにしてしまえば動くものはできます。実際はDBと接続するでしょう。

// Model.swift
extension ToDo{
    static var todoStore = [ToDo]()
    static var nextId :Int = 0
    static let workerQueue = DispatchQueue(label: "worker")
    
    static func storeHandler(todo: ToDo, completion: (ToDo?, RequestError?) -> Void ) {
        var todo = todo
        if todo.completed == nil {
            todo.completed = false
        }
        todo.id = nextId
        todo.url = "http://localhost:8080/\(nextId)"
        nextId += 1
        execute {
            todoStore.append(todo)
        }
        completion(todo, nil)
    }
    
    static func deleteAllHandler(completion: (RequestError?) -> Void ) {
        execute {
            todoStore = [ToDo]()
        }
        completion(nil)
    }
    
    static func getAllHandler(completion: ([ToDo]?, RequestError?) -> Void ) {
        completion(todoStore, nil)
    }
    
    static func getOneHandler(id: Int, completion: (ToDo?, RequestError?) -> Void ) {
        completion(todoStore.first(where: {$0.id == id }), nil)
    }
    
    static func updateHandler(id: Int, new: ToDo, completion: (ToDo?, RequestError?) -> Void ) {
        guard let idMatch = todoStore.first(where: { $0.id == id }),
            let idPosition = todoStore.index(of: idMatch) else { return }
        var current = todoStore[idPosition]
        current.user = new.user ?? current.user
        current.order = new.order ?? current.order
        current.title = new.title ?? current.title
        current.completed = new.completed ?? current.completed
        execute {
            todoStore[idPosition] = current
        }
        completion(todoStore[idPosition], nil)
    }
    
    static func deleteOneHandler(id: Int, completion: (RequestError?) -> Void ) {
        guard let idMatch = todoStore.first(where: { $0.id == id }),
            let idPosition = todoStore.index(of: idMatch) else { return }
        execute {
            todoStore.remove(at: idPosition)
        }
        completion(nil)
    }
    
    static func execute(_ block: (() -> Void)) {
        workerQueue.sync {
            block()
        }
    }
}
// Application.swift
router.get("/", handler: ToDo.getAllHandler)
router.get("/", handler: ToDo.getOneHandler)
router.post("/", handler: ToDo.storeHandler)
router.delete("/", handler: ToDo.deleteAllHandler)
router.delete("/", handler: ToDo.deleteOneHandler)
router.patch("/", handler: ToDo.updateHandler)

終わりに

次は、Bluemix上でこのアプリを動作させる方法について書こうかと思います。

Kituraのチュートリアル(ToDoBackend)を試してみた

はじめに

こんにちは。toosaaです。

前回IBM製のフレームワークKituraのGetting Startedに従ってサーバーサイドのHello, World!を行いました。今回は、Getting Startedに続いてチュートリアルとして用意されているToDoBackendを試してみます。そして次回、その内容の一部を調査しようと思います。

動作環境

  • macOS High Sierra ver 10.13.3
  • Xcode ver 9.2
  • Homebrew 1.5.2

事前準備

ToDoBbackendに対して、todo-backend-js-specによるテストをグリーンにしていくことで開発していきます。テスト駆動ですね。そのため、ToDoBackendとtodo-backend-js-specの二つのプロジェクトを用意します。

  • $ git clone http://github.com/IBM/ToDoBackend
  • $ git clone http://github.com/TodoBackend/todo-backend-js-spec

試しにテストを実行してみる

  • $ cd path/to//todo-backend-js-spec
  • $open index.html
  • 起動したブラウザのページのtest target rootにhttp://localhost:8080をセット
  • run testsをクリック

これでテストを実行できます。おそらく、最初の行にthe api root responds to a GET (i.e. the server is up and accessible, CORS headers are set up)とエラーが出てると思います。サーバーを起動してないのだからエラー出て当然です。

ToDoBackendのサーバーを初期化する

  • $ cd path/to/ToDoBackend
  • $ mkdir ToDoServer
  • $ cd ToDoServer
  • $ kitura init
  • open ToDoServer.xcodeproj
  • ビルドスキームをToDoServerに変更(デフォルトではToDoServer-Packageになっているはず)
    • Xcode左上の停止ボタン(四角いやつ)の右
  • ⌘-Rで実行
  • 再度テストを実行する

おそらくまたthe api root responds to a GET (i.e. the server is up and accessible, CORS headers are set up)とエラーが出てると思います。 Cross Origin Resource Sharing (CORS) が有効になっていないからです。

Cross Origin Resource Sharing (CORS) を有効化する

  • CORSライブラリをPackage.swiftに追加する
    • (注)チュートリアルページではdepenciesの方にConfigurationとかあり、エラーになった
// Package.swift
let package = Package(
    name: "ToDoServer",
    dependencies: [
      .package(url: "https://github.com/IBM-Swift/Kitura.git", .upToNextMinor(from: "2.0.0")),
      .package(url: "https://github.com/IBM-Swift/HeliumLogger.git", .upToNextMinor(from: "1.7.1")),
      .package(url: "https://github.com/IBM-Swift/CloudEnvironment.git", from: "6.0.0"),
      .package(url: "https://github.com/RuntimeTools/SwiftMetrics.git", from: "2.0.0"),
      .package(url: "https://github.com/IBM-Swift/Health.git", from: "0.0.0"),
      //ここ追加
      .package(url: "https://github.com/IBM-Swift/Kitura-CORS", .upToNextMinor(from: "2.0.0")),
    ],
    targets: [
      .target(name: "ToDoServer", dependencies: [ .target(name: "Application"), "Kitura" , "HeliumLogger"]),
      //ここにKituraCORS追加
      .target(name: "Application", dependencies: [ "Kitura", "KituraCORS", "CloudEnvironment", "Health" , "SwiftMetrics",
                                                   ]),
      .testTarget(name: "ApplicationTests" , dependencies: [.target(name: "Application"), "Kitura","HeliumLogger" ])
    ]
)
  • packageをインストールするためにregenerateする
    • Xcodeを閉じる
    • $ cd path/to/ToDoBackend/ToDoServer
    • $ swift package generate-xcodeproj
    • $ open TodoServer.xcodeproj
  • CORSライブラリを使う
    • Sources/Application/Application.swiftを開く
    • import KituraCORSを追記
    • postInit()の中に下記のコードを追加
let options = Options(allowedOrigin: .all)
let cors = CORS(options: options)
router.all("/*", middleware: cors)

この状態で再度テストを行うと、the api root responds to a POST with the todo which was posted to itに変わっているはずです。(注)もし変更なかったら、テスト用のページを一旦戻りrun testsしてみると変わるかと思います。このテスト結果はPostリクエストの処理を実装してないためです。

Postリクエストの処理を実装する

  • Modelを生成する
    • XcodeのProjectNavigator上のApplicationフォルダに右クリック
    • New File...を選択する
    • 名前をModels.swiftにする
    • TargetsをApplicationだけにする(おそらくToDoServerPackageDescriptionにチェックがついている)。
      • 私の環境だと最初はTargetsを選択する領域が出てなかったのですが、何かのボタンを押したら出たと記憶しています
    • Create
    • 下記のコードを追加
public struct ToDo : Codable, Equatable {
    public var id: Int?
    public var user: String?
    public var title: String?
    public var order: Int?
    public var completed: Bool?
    public var url: String?
    
    public static func ==(lhs: ToDo, rhs: ToDo) -> Bool {
        return (lhs.title == rhs.title) && (lhs.user == rhs.user) && (lhs.order == rhs.order) && (lhs.completed == rhs.completed) && (lhs.url == rhs.url) && (lhs.id == rhs.id)
    }
}
  • ToDoアイテムを格納するArrayを作る(このチュートリアルではDBは使わない)

    • Sources/Application/Application.swiftを開く
    • let cloudEnv = CloundEnv()の下に下記のコードを追加する

      private var todoStore = [ToDo]()
      private var nextId :Int = 0
      private let workerQueue = DispatchQueue(label: "worker")
      
    • App Class内に下記メソッドを追加

      func execute(_ block: (() -> Void)) {
         workerQueue.sync {
           block()
         }
      }
      
  • / へのpostリクエストに対応するhandlerを登録する

    • postInit()に下記コードを追加する
router.post("/", handler: storeHandler)
  • App Classに下記のメソッドを追加する
func storeHandler(todo: ToDo, completion: (ToDo?, RequestError?) -> Void ) {
     var todo = todo
     if todo.completed == nil {
         todo.completed = false
     }
     todo.id = nextId
     todo.url = "http://localhost:8080/\(nextId)"
     nextId += 1
     execute {
         todoStore.append(todo)
     }
     completion(todo, nil)
 }

DELETE, GET, PATCHの実装もする

基本は、postと同じ感じなので割愛

終わりに

今回は、Kituraのチュートリアルの一つToDoBackendを試してみました。次回は、この中で調査したものについて書きます。

Kituraを使ってサーバーサイドSwiftを試してみた

はじめに

すごく久しぶりに投稿します。今後は、友人たちと投稿しようと思います。

今回は、Swiftを使ってサーバーサイドを書いてみたいと思い、IBM製フレームワークのKituraを使ってみた件について、toosaaがお話しします。

内容としては、Getting Startedを動かしてみるまでと、ちょっとした調査となります。

動作環境

  • macOS High Sierra ver 10.13.3
  • Xcode ver 9.2
  • Homebrew 1.5.2

Getting Started

Getting Startedの手順に従います。2017年2月3日現在、同じことを書いているだけです。

事前準備

Kitura コマンドラインのインストール

  • $ brew tap ibm-swift/kitura
  • $ brew tap ibm-swift/kitura

プロジェクトの初期化

  • $ HelloKitura
  • $ cd HelloKitura
  • $ kitura init
    • ここにあるように色々なファイルが作られます
      • .gitignoreとかもあります
      • この記事書いている時に、このページにCRUD projectやmodel generatorというのがあるのに気づきました。あとで読みます。

Hello, World!

  • $ open HelloKitura.xcodeproj
  • Sources/Application/Application.swiftpostInit()メソッドの中に下記のコードを挿入

注) 私の環境ではimport Kiture のとこにNo such module 'Kitura'とエラー出ましたが普通にビルドできます。

// Handle HTTP GET requests to /
router.get("/") {
    request, response, next in
    response.send("Hello, World!")
    next()
}
  • ビルドスキームをHelloKituraに変更(デフォルトではHelloKitura-Packageになっているはず)
    • Xcode左上の停止ボタン(四角いやつ)の右
  • ⌘-Rで実行
  • http://localhost:8080 にアクセス

これだけです。

調査

Hello, World!で書いたコードについて調べてみます。

routeの指定

routerはRouter Classのインスタンスで、getのオーバーロードをいくつか持ってます。そのメソッドの宣言を引っ張って来ると下記の通りです。参考

//RouterHTTPVerbs_generated.swift
public func get(_ path: String?=nil, handler: RouterHandler...) -> Router
public func get(_ path: String?=nil, handler: [RouterHandler]) -> Router
public func get(_ path: String?=nil, allowPartialMatch: Bool = true, middleware: RouterMiddleware...) -> Router 
public func get(_ path: String?=nil, allowPartialMatch: Bool = true, middleware: [RouterMiddleware]) -> Router

今回使っているのは、これらの一つですが、下記のメソッドもあります。チュートリアルで使われています。

//CodableRouter.swift
public func get<O: Codable>(_ route: String, handler: @escaping CodableArrayClosure<O>)
public func get<O: Codable>(_ route: String, handler: @escaping SimpleCodableClosure<O>)
public func get<Id: Identifier, O: Codable>(_ route: String, handler: @escaping IdentifierSimpleCodableClosure<Id, O>)
public func get<Q: QueryParams, O: Codable>(_ route: String, handler: @escaping (Q, @escaping CodableArrayResultClosure<O>) -> Void)

上のget()は、クロージャーを使っていますが、@escapingがついていない&返り値があるので同期処理なのだろうと思います。反対に下のは、非同期処理で使うのだろうと思います。あとCodableなのでjsonのシリアライズでシリアライズ。

どちらにせよ、第一引数の文字列がurlに関るはずです。名前がrouteとpathで違うのはわかりませんが。

http responseを返す

Hello, Worldではresponse.send("Hello, World!")でhttp responseを返していると予想できます。このresponseはRouterResponse classのインスタンスです。このclassのsend()は下記がとなっています。参考

public func send(_ str: String) -> RouterResponse
public func send(data: Data) -> RouterResponse
public func send(fileName: String) throws -> RouterResponse
public func send(json: [Any]) throws -> RouterResponse
public func send(json: [String: Any]) throws -> RouterResponse
public func send(status: HTTPStatusCode) -> RouterResponse
public func send<T : Encodable>(_ obj: T) throws -> RouterResponse
public func send<T : Encodable>(json: T) throws -> RouterResponse
public func send<T : Encodable>(jsonp: T, callbackParameter: String = "callback") throws -> RouterResponse

今回は一番最初のstringを返しています。静的なhtmlならsend(fileName: String), web apiならjsonのを使えば良さそうです。

next?

RouterHandlerの第三引数で、@escaping () -> Voidというクロージャーです。ドキュメントでは下記のように書かれています。ちょっと、コードを読んでみましたが、よくわかっていません(・_・;)

The closure to invoke to cause the router to inspect the path in the list of paths.

終わりに

今回は、KituraのGetting Startedと、その中で出てきたコードの簡単な調査を話しました。次回は、チュートリアルの内容に触れようと思います。

youtube-ios-player-helperで再生できないことがある

はじめに

youtube の動画でたまに再生されないものがあったので、それについてのお話

現象

youtube-ios-player-helperを使ってyoutubeの動画再生していました。 大半の動画は再生されるのですが、一部の動画(ex. ID: A_bdeYlpasE)で再生されませんでした。 再生されないというのも、押した反応はあるのですが、その後何も起きないという感じでした(下図参照)。 https://gyazo.com/6f3708a2a932c326362ada6904dfc047

また、全デリゲートメソッドを監視してみましたが特にエラーが出たりもしませんでした。

原因と対処法

何回もやっていたら偶然か違う挙動をしました。

f:id:k-s-9190118:20161111105648p:plain

これを元にライブラリのgithubを調べてみたら、issueが幾つか上がってます#197。 対応としてはパラメーターにoriginを追加すれば良いようです。

self.playerView?.load(withVideoId: "A_bdeYlpasE", playerVars: ["playsinline":1, "origin" : "https://www.youtube.com"]

Mac Book Pro頼んだら決済が通ってなかった

はじめに

先月とうとう発表された新Mac Book Proを意気揚々と予約して今か今かと待ちわびてたんですが ふとApple Storeのご注文一覧のページにステータスの確認をしに行ったら お支払い確認中となってましたorz

状況確認

  • 発表された日にMac Book Proを予約
  • クレカで12分割払い
  • Appleの右上のご注文からご注文一覧見に行った
  • なんか赤い文字でエラーっぽい
  • クレカ情報入れ直したり別のでやったけどダメ
  • メール見に行ったけど何も来てなかった

お支払い確認中

お支払いの確認または承認の問い合わせ中です。この時点では正式なご注文として成立しておりません。ご注文から14日以内(後払いは30日以内)にお支払いまたはお手続きをお願いします。期日までにお支払いの確認または承認が取れない場合は、ご注文は自動的にキャンセルとなります。

ショッピングのサポートより

何かしないと自動でキャンセルになってしまうので自分から動きましょう

対応

ご注文一覧のページでは何もできなかったのでAppleのサポートに電話しました。 0120-993-993

自動案内でまず4を押し,ご注文一覧ページにあるWから始まる注文番号のWを除いた数字を入力すると担当者につながります。

状況を説明したらクレカの上限とかで決済できなかったのでは?と確認されたがWebで確認した感じ上限は残っていましたし、複数のクレカでダメだったのでそう伝えました。 すると、こういう場合、分割払いにしているのを1割に変更すると通る人が多いと返答がきました。分割から少ない分割にするということはできず、分割から1割に変更するということしかできないそうなので、1割に変更してもらいました。この結果きちんと決済が通りました。よかったε-(´∀`*)ホッ

Reporterを使ってiTunes ConnectのSales and Trendsを自動で取得する - 解析編

はじめに

今回はReporterを使ってiTunes ConnectのSales and Trendsを自動で取得する - 設計編におけるdaily reportの"iTunes Connectから取得"と"インストール数の取得"のサンプルを実装しようと思います。最終的な実装とはちょっと違いますが基本は同じです。

環境

環境 version
OS OS X EL Capitan 10.11.6
ruby 2.3.1(rbenv)

実装方針

  1. シェルコマンドを実行してreporterを実行
  2. Zlib::GzipReaderを使ってgzファイルを解凍
  3. ターゲットとしているアプリかつProduct typeのunitsをかき集める

シェルコマンドを実行してreporterを実行

reporter自体はjavaで実行なのでrubyからシェルコマンドを実行する仕組みを実装します。Rubyで外部コマンドを実行して結果を受け取る方法あれこれを見るといろいろあるみたいですけど一番簡単そうなバッククォートで良さそうです。

date = "20161101"
report_xml = `java -jar Reporter.jar p=Reporter.properties m=Robot.XML Sales.getReport <vendor id>, Sales, Summary, Daily, #{date}` 

Zlib::GzipReaderを使ってgzファイルを解凍

サンプル見るのが早いかと思います。

require 'zlib'

date = "20161101"
file_name = "S_D_<vendor id>_#{date}.txt.gz"

Zlib::GzipReader.open(file_name){|gz|
# 最初のlineはムシ
gz.gets

while s = gz.gets
    p s
}

ターゲットとしているアプリかつProduct typeのunitsをかき集める

今回は簡略化のため一つのアプリに限定してます。
ここで問題となるのが、取得できるデータが各アプリのインストール毎という形式ではなく、各アプリのProduct Typeや地域毎という感じで別れてしまっていることです。今回は、ターゲットとしたアプリの全地域でのインストール数を取得するという方針でいきます。

require 'zlib'

# http://help.apple.com/itc/appssalesandtrends/#/itc0c699d615
# アップデートとかを集計しないため
TargetProductIds = ["1", "1F", "1T"].freeze
TargetBundleId = "hogehoge".freeze

date = "20161101"
file_name = "S_D_<vendor id>_#{date}.txt.gz"
app_unitis = 0

Zlib::GzipReader.open(file_name){|gz|
    # 最初のlineはムシ
    gz.gets

    while s = gz.gets
        # tsvでタグ区切りなので
        rows = s.split("\t")

        # 正確にはSKUだったけどほとんどの場合bundle_idと同じにするだろうからこのままでorz
        bundle_id = rows[2]
        units = rows[7].to_i
        product_type_id = rows[6]

        if !TargetProductIds.include?(product_type_id)
            next
        end

        if bundle_id == TargetBundleId
            app_units += units
        end
    end
}

p app_units

コード全部

require 'rexml/document'
require 'zlib'
# http://help.apple.com/itc/appssalesandtrends/#/itc0c699d615
# アップデートとかを集計しないため
TargetProductIds = ["1", "1F", "1T"].freeze
TargetBundleId = "hogehoge".freeze
VendorId = 1111111.freeze

date = "20161101"
report_xml = `java -jar Reporter.jar p=Reporter.properties m=Robot.XML Sales.getReport #{VendorId}, Sales, Summary, Daily, #{date}` 

report_result = REXML::Document.new(get_report_xml)
unless report_result.elements['Error/Code'].nil?
    p get_report_result.elements['Error/Code'].text
    p get_report_result.elements['Error/Message'].text
    # TODO: きちんと例外処理
    raise "Reporterのダウンロードに失敗しました"
end

file_name = "S_D_#{VendorId}_#{date}.txt.gz"
app_unitis = 0

Zlib::GzipReader.open(file_name){|gz|
    # 最初のlineはムシ
    gz.gets

    while s = gz.gets
        # tsvでタグ区切りなので
        rows = s.split("\t")

        # 正確にはSKUだったけどほとんどの場合bundle_idと同じにするだろうからこのままでorz
        bundle_id = rows[2]
        units = rows[7].to_i
        product_type_id = rows[6]

        if !TargetProductIds.include?(product_type_id)
            next
        end

        if bundle_id == TargetBundleId
            app_units += units
        end
    end
}

p app_units

SpreadsheetをRubyで操作する

はじめに

Reporterを使ってiTunes ConnectのSales and Trendsを自動で取得する - 設計編で実装指針としては書いていたGoogle SpreadsheetとRubyの連携について書きます。
なお環境は下記の通りです。

環境 version
OS OS X EL Capitan 10.11.6
ruby 2.3.1(rbenv)

ライブラリ(gem)

google_driveというgemを利用します。参考ページは下記の通りです。

実装

事前準備

誰でも好き勝手にSpreadsheetを操作できるようにしてしまうのはまずいので認証が必要です。そのための準備をします。

サービスアカウントの作成

On behalf of no existing users (service account)こちらの手順に従ってもらえれば、認証用のjsonファイルがダウンロードされます。

スプレッドシートの共有設定

先ほど作成したサービスアカウントのメールアドレス(jsonに書いてあります)を操作対象のSpreadsheetで共有します。

bundlerのインストール

gemの管理をbundlerを利用したいのでbundlerをinstallします。

  • $ cd path/to/directory
  • $ rbenv exec gem install bundler

google_driveのインストール

Gemfileというファイルを作成して下記を書き込みます。

source 'https://rubygems.org'
gem 'google_drive'

そしてgemをインストールします。
$ bundle install --path vendor/bundle

Example実装

Example to read/write spreadsheetsを実行してみます。

準備

  • サービスアカウントを作成した時にダウンロードしたjsonをrubyプログラムと同じディレクトリに config.jsonという名前で置きます
  • 操作対象のスプレッドシートキーを取得します
    • https://docs.google.com/spreadsheets/d/<スプレッドシートキー>/editみたいな感じになってるのでそこから取得できます(ちゃんとどこかに書いてあるとは思いますが...)

サンプルコード

自分はgithubのコードにrequire 'bundler/setup'を追加しないと動きませんでした。またsessionの作り方もサービスアカウントを使う場合に書き換えています。

require 'bundler/setup'
require "google_drive"

# Creates a session. This will prompt the credential via command line for the
# first time and save it to config.json file for later usages.
session =  GoogleDrive::Session.from_service_account_key("config.json")

# First worksheet of
# https://docs.google.com/spreadsheet/ccc?key=pz7XtlQC-PYx-jrVMJErTcg
# Or https://docs.google.com/a/someone.com/spreadsheets/d/pz7XtlQC-PYx-jrVMJErTcg/edit?usp=drive_web
ws = session.spreadsheet_by_key(<スプレッドシートキー>).worksheets[0]

# Gets content of A2 cell.
p ws[2, 1]  #==> "hoge"

# Changes content of cells.
# Changes are not sent to the server until you call ws.save().
ws[2, 1] = "foo"
ws[2, 2] = "bar"
ws.save

# Dumps all cells.
(1..ws.num_rows).each do |row|
  (1..ws.num_cols).each do |col|
    p ws[row, col]
  end
end

# Yet another way to do so.
p ws.rows  #==> [["fuga", ""], ["foo", "bar]]

# Reloads the worksheet to get changes by other clients.
ws.reload

APIピックアップ

Session作成

該当箇所: session = GoogleDrive::Session.from_service_account_key("config.json")

GoogleDrive/Sessionにあるように認証のやり方でメソッドが変わります。今回はサービスアカウントを利用したためExampleとは違い.from_service_account_key(json_key_path_or_io, scope = DEFAULT_SCOPE) ⇒ Objectを利用しました。

Spreadsheetの取得

該当箇所: session.spreadsheet_by_key(<スプレッドシートキー>)

GoogleDriveとかから見えるスプレッドシートの単位だと思います。キー以外にtitleとurlからも取得できます(参考)

WorkSheet取得

該当箇所: ws = session.spreadsheet_by_key(<スプレッドシートキー>).worksheets[0]

スプレッドシート内でのシート(画面左下の+ボタンで追加できるもの)のことのようです。サンプルでは0番目のシートを取得していますが、titleやgid(urlを見るとわかります)などから取得することも可能です(参考)

WorkSheetの操作

GoogleDrive/Worksheet

セルの値取得

例えば C2の値をとるならば c2 = ws[2, 3]とやれば取れます。簡単ですね。

セルの値変更

C2に"てくてくテック"と入れる場合は下記のようになります。

ws[2, 3] = "てくてくテック"
ws.save

最後の行や列の番号

ws.max_colsws.max_rowsで取得できます

行の挿入

#insert_rows(row_num, rows) ⇒ Objectを使います。row_num行目にrows行追加します。
e.g(ドキュメントより)

# Inserts 2 empty rows before row 3.
worksheet.insert_rows(3, 2)
# Inserts 2 rows with values before row 3.
worksheet.insert_rows(3, [["a, "b"], ["c, "d"]])