メモるブログ

技術メモを書いていく所存

Haskellメモ: Salesforceでクエリするまで

ここのgistに至るまでのメモ

haskell-salesforce.hs · GitHub

データ定義。ログインに必要なデータ型を定義。
LoginResultとしてセッションIDとかもひとまとめにしておく

data LoginRequest = LoginRequest{
  username :: String,
  password :: String,
  endpoint :: String,
  apiVersion :: String
} deriving Show

data LoginResult = LoginResult{
  sessionId :: String,
  userId :: String,
  serverUrl :: String,
  metadataServerUrl  :: String,
  instanceUrl :: String,
  resApiVersion :: String
} deriving Show

クエリリザルトとレコードの型定義。
fromJSONはちゃんと型定義が必要なので、もしライブラリ化するとなるとAccountとかのデータ型は随時ライブラリ利用者側で作ってもらう必要がありそう

data QueryResult = QueryResult{
  done :: Bool,
  totalSize :: Int,
  records :: [Account]
} deriving Show

instance FromJSON QueryResult where
  parseJSON (Object v) = QueryResult
    <$> v .: "done"
    <*> v .: "totalSize"
    <*> v .: "records"
  parseJSON _ = mzero

data Account = Account{
  sfid :: String,
  name :: String
} deriving Show

instance FromJSON Account where
  parseJSON (Object v) = Account
    <$> (v .: "Id")
    <*> (v .: "Name")

上の書き方だと上の属性から順に定義していくっぽい。JSONの属性名とデータ型の属性名は合わせなくてもOK。

上の属性から順に定義するのがダルかったり、色々手を加えたい場合はdoでいろいろやる

{-# LANGUAGE RecordWildCards #-}

instance FromJSON QueryResult where
  parseJSON = withObject "QueryResult" $ \v -> do
    records <- v .: "records"
    totalSize <- v .: "totalSize"
    done <- v .: "done"
    return QueryResult{..}

RecordWhileCardsは{..}っていう記法でrecords=records, totalSize=totalSize, ...みたいにワイルドカード展開してくれる神機能

Generic使うともっと楽でこれだけでOKっぽい

{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics

instance FromJSON QueryResult where

Aesonの使い方はここが詳しい→ https://artyom.me/aeson

main部分。IO型は特殊な感じ。副作用あれこれ

main :: IO ()
main = do

コマンドライン引数はgetArgsで

args <- getArgs

環境変数はgetEnvで

username <- getEnv "SALESFORCE_USERNAME"

なんかデバッグしたい場合はSystem.IO.printで

System.IO.print loginResult

IOを返すけど戻り値を無視すると print XXX >> fでprint XXX >>= _ -> と等価だった(気がする)
なので連続して実行するのと変わらないって感じ

文字列replaceはそんな関数用意されいないっぽいので自前で

replace :: String -> String -> String -> String
replace x y src = inner src where
  inner [] = []
  inner str@(s:ss)
    | L.isPrefixOf x str = y ++ inner (L.drop (L.length x) str)
    | otherwise = s:inner ss

isPrefixOfで合致したらその文字列を削る L.drop (L.length) してその文字列をくっつけて y ++ それを再帰的に実行すればOK

HTTPリクエスト、正直よくわからないけどこんな感じでできた

import Network.HTTP.Conduit
import Network.URI
import Network.URI.Encode as URI

initReq <- parseRequest $ "https://" ++ endpoint ++ "/services/Soap/u/" ++ apiVersion
manager <- newManager tlsManagerSettings
response <- httpLbs req manager

XMLパースはもっとよくわからない。

let Document _ _ root _ = xmlParse "" $ (BL8.unpack (responseBody response))
     cont = CElem root noPos
     result = xtract id "/soapenv:Envelope/soapenv:Body/loginResponse/result" cont !! 0
     sessionId = tagTextContent $ xtract id "/result/sessionId" result !! 0

Data.ByteString.Char8 とか Data.ByteString.Lazy.Char8とか Data.Text とかあんまよくわかってない…

とりあえずpackとかunpackとかで変換かけれるっぽい

あとOverloadedStringsを使うと文字列リテラルでTextやByteStringを定義できるようになって便利。IsStringインスタンスが定義されていれば何でもいける…?

{-# LANGUAGE OverloadedStrings #-}

NamedFieldPuns使うとPerson{name=name} みたいな冗長な記述を排除できて便利

{-# LANGUAGE NamedFieldPuns #-}

return LoginResult{sessionId, userId, ...}

URIエンコードするときはNetwork.URI.Encode.encodeで

let queryUrl = instanceUrl lr ++ "/services/data/v" ++ resApiVersion lr ++ "/query/?q=" ++ URI.encode query

Data.Aeson.decodeはMaybe型を返す

let result = JSON.decode body :: Maybe QueryResult

正規表現 (他にもライブラリあるっぽいしパーサコンビネータであれこれできるっぽいけどregex-posixを利用)

import Text.Regex.Posix

matches = serverUrl =~ ("^(https://[^/]*)/.*" :: String) :: [[String]]

2018-05-10

記事紹介

Adding Automated Speech Recognition for Phone Calls to Ruby on Rails Applications

  • TwilioとRailsの連携について
  • User(Phone) => Twilio => Rails => Twillio => Phone という流れ
  • Rails => Twilioの連携はTwiMLで行う
  • twilio-rubyのgemを使うと簡単にTwiMLを生成可能

Rails API and Facebook login (featuring Doorkeeper and OAuth 2 authorization)

  • モバイルからのfacebook認証をdoorkeeperを使って実装する
  • モバイルがfacebookからaccess_tokenを取得 => doopkeeperにassertionとしてaccess_tokenを投げて認証する、という流れ
  • OAuthのassertion grantを使っている(このgrant、初めて知った…)

Writing Less Error-Prone Code

  • Timecopはblockを使うとbefore/afterでやるよりはミスが少ない(Dir.chdirとかFile.openでも使われてる)
  • 簡潔さより読みやすさ

A Simple Way to Decrease Complexity of Routes in Rails

  • routes.rbを分割して管理しやすくする方法
  • config.paths['config/routes.rb'] = Dir[Rails.root.join('config/routes/*.rb')]

2018-06-05

記事紹介

Dynamically setting default_url_options in Capybara

  • Capybaraでdefault_url_options[:host]や[:port]を書き換える話
  • xxx_pathを使うのであんまり問題になったこと無いな…

Good Things Come to Those Who Await

  • async/awaitの話
  • asyncのfunctionはPromiseを返すの知らなかった…
  • asyncなfunctionの外側ではthen, catchが必要

2018-06-01

RubyKaigi 2日目。今日も超絶楽しかった

My way with Ruby

  • 圧倒的アウトプット・アクティビティだった…

Faster Apps, No Memory Thrash: Get Your Memory Config Right

  • メモリ消費量抑える話
  • そもそも無駄にクラス生成してメモリアロケートしないこと、環境変数のチューニング、jemalloc、最新のRubyを使う
  • やっぱjemallocかーって感じだった

Guild Prototype

  • Rubyでパラレル処理する機構の話
    • GuildはGVLの挙動を変える変更(という理解)。マルチスレッドでもマルチコアを活かせるようになる。
      • 今まで: Process -> Thread -> Fiber
      • これから:Process -> Guild -> Thread -> Fiber
    • Guildというのはプロジェクト名なので実際の名前は検討中らしい
    • Actorモデル(スレッドとか意識せずにモデルレベルでメッセージパッシングしていく感じ)
  • 所感
    • マルチコアを使い切りたいというのは数値計算をマルチコアでスケールさせたいという感じの要件から来てるのかなーと思った。webのコンテキストだと、現状でもコンカレントに動いてIOバウンドな処理に対しては実用上問題ないレベルでスケールする気がするし、pumaとかはマルチプロセス・マルチスレッドで動くのでサーバーリソースは今のRubyの仕様でも十分使い切れる気がするので、Guildが入ったからと言ってwebのパフォーマンスが改善させるとかそういう話ではないと思う。
    • GVLのロックをかけずにセキュアにマルチコアでスレッドを動かす感じだと思うが(そのためにmutable/imutableなオブジェクトの扱いや共有するか否かのあれこれが必要)、GCの扱いが結構変わりそうだしRubyに入るのはもう少し後になりそうだなーと思った

Ruby Programming with Type Checking

  • steepの話
  • sorbetとは違うアプローチで、外部ファイルに型情報を定義して、実ファイル内にアノテーションを書く、というスタイルでの型チェック。sorbetは実ファイルにsignature(=コード。コメントではない)を書くスタイル
  • 型情報定義面倒そうだなーと思ったけどscaffold的な型定義ジェネレータなコマンドがある(全部、型がanyになるけど…)

RNode with code positions

  • ASTのノードの構造体であるRNodeにコードポジション(行とか列とか)を付与したので色々できるようになったよ、という話
  • NoMethodErrorでこういう表記ができるようになる(barが無い場合)。ASTのノードにコードポジション付与して、ASCIIでグラフィカルに良い感じに表示ってpower-assertっぽいなーと思った
foo.bar
   ^^^^

Type Profiler: An analysis to guess type signatures

  • 型チェックのアプローチの話
  • 1番目: メソッド内で対象の引数に対してAメソッドが呼ばれていたら、対象の引数はAメソッドを持つ引数だ!という感じで引数を推測する方式
  • 2番目: メソッド内で a = b + 1という式があったら、B#+(NUM) 戻り値A、となるようなメソッドを持つクラスを探してBとAの型を推定する方式
  • 3番目: IntelliJがRubyTypeInferenceというのを進めている。テスト回してメソッドに与えられた引数のクラスとかを見て、メソッドの引数の型を推測する方式。
  • TypeDB
    • 型定義情報を定義するDB。型チェックはいろんなアプローチがあるが、TypeDB経由で3rd partyの型チェッカを動かせるようにできるように、という構想。例えばTypeDBを使ってsteepの型情報ファイルを生成したり、IDEに型情報をフィードバックして精度上げたり

2018-05-31

今日はRubyKaigi@仙台でした。聞いた内容をざっくり

KeyNote

  • 名は体を表す(名前重要)
    • 概念を理解していないと名前を付けられない => 響きました…。もっと理解し、考えます。
  • Ruby is DeadとかRails is Deadとか言われるのあれなのでもっとコミュニティに貢献していこうと思いました

Reducing Memory Usage in Ruby

TypeChecker

  • RubyでTypeチェッカー実装した話
  • https://sorbet.run/
  • まだ非OSS
  • stripeではすでに使われている様子
  • 個人的には型を明示的に書くの好きじゃないので型推論のアプローチが好きなんだが実装がとても気になる。OSSになったらぜひコードを読みたいし、ちょっとしたプロジェクトに入れ込みたい。
  • パフォーマンスも良さそう

Rubocop

  • 最初はregular expressionでやってたらしい
  • そっからRipper使って、Ripper辛くなって今はparser使っている

Parser based syntax highlight

1日目まとめ

  • 懇親会とかも含めてやっぱりもっとリアルのコミュニティに出て発信するなりコミュニケーションしていかないと損だなーと思った。
  • ブログとかそういうオンラインでのアウトプットも必要だけど、やっぱりリアルも大事
  • RipperとかParserとかのAST解析してそっから云々するライブラリの仕組み、例えばrubocopとかiroとかの仕組みが全然わからなかったのでもうちょっと勉強した方がもっと面白く聞けたと思う。
  • あと英語大事…。リスニング全然できなくてslideを本気で読むマシンになっていた

2018-05-29

記事紹介

TestProf: a good doctor for slow Ruby tests

TestProf II: Factory therapy for your Ruby tests

  • TestProfで遅いところを探せるgem
  • factoryのcascadeは結構重いらしい
  • cascadeを減らすためのアプローチが色々有ってメリデメ比較してて面白い

Ruby 2.4 adds Comparable#clamp method

  • clampの紹介。最大、最小を超える場合はその範囲に寄せてくれるらしい。便利っぽい

Running Chrome Headless, Selenium and Capybara inside GitLab CI and Docker

  • chrome headlessがdockerで動かないときの対処法について
  • sandboxのやつです

kevinrood/json_key_transformer_middleware

  • JSはlowerCamelでRubyはsnakeなのでAPIリクエスト時にlowerCamelをsnakeに変換してよしなにやってくれるらしい
  • これ地味に便利そう

alekseyl/niceql

  • SQLフォーマッタ
  • 超便利そう

Conditional execution with DSL in Ruby

  • DSL
  • callの.()記法、私もめっちゃ好き
  • on_success, on_failureはよさげ

2018-05-28

記事紹介

Actionable Tips to Improve Web Performance with Rails

  • Rails(というかHTTPレベルも含めた)パフォーマンス・チューニング話
  • resource hintingは使ったこと無いのでちょっと使ってみようかなー

Fetch vs. Axios.js for making http requests

  • fetch()とaxiosの違い
  • 結構fetchの挙動アレっぽいのでaxios使ったほうがよさげ。json()とか確かにめんどいと思った