「pdf fileの10ページ目を切り取ってpngにconvertする」やり方。 pdftk
で10
ページ目をcat
して標準出力に渡し、それをpipeで繋げてpdftoppm
で標準入力から読み込みredirectして-png
で変換してfileへ。-png
がないと.ppm
になるので注意。 1
pdftk nenga2021.pdf cat 10 output -|pdftoppm -png - > nenga2021.png
Author: U-ryo
Cut Pdf and Convert to Png
Learned on Ruby and Vue, Angularjs
お仕事でRuby on RailsとVueをやっております。 これまではずっとJavaやAngularな人生だったので、Duck Typingと格闘中です。 新規機能開発で様々な山を超えてきた(知見を得た)ので、忘れないうちに。
- 破壊的method(Bang Method)って、その響きから何かあんまり良くないのかと無意識に思っていたのですけれども、調べてみると特に忌避すべきというものは見つかりませんでした。内部logicを考えると中で変数を付け替えていると思われますが、bangしないと両方分memoryに持ってなきゃならないから?→そうしたらrubocopに、
result[:key]=... if ...
と書けと言われました。ナルホドです! - 手元では
yarn test:vue
通るのにCircleCIでは7 fails
と言われて通らないので悩みました。どう目を凝らしてもerrorらしき出力はなく。よく見るとCircleCIの最後にToo long with no output (exceeded 10m0s): context deadline exceeded
とあり、かかった時間が10:57とか。えー!?そんなにかかっているの?? 手元でtime
で計測するも、1分ちょっと。CircleCI再実行して観察すると、test自体は1分程で終わり、その後ずっとだんまりで、10分程してTimeoutします。何だろう? 手元では再現しないのでとにかく厄介です。考えてみたら、チェックじゃなくて「1)」とか「2)」になっているところが失敗したところなんですね。確かに7)までありますわ。でも、そこがどう間違っているのかが全然出力されないので皆目わかりません。一箇所、describe('...', async () => {
ってなっていたから、これか! と消して勇躍試してみたものの、症状変わらず。mount
をshallow
にしてみたり削れるだけのasync
/await
を削っても。何故かdescribe
の囲みを解くとtestが失敗します。期待してるobjectのpropsがないと。でももう一つ別のpropsはある。もぅわけわかんない!ですよ。CircleCIにsshで入ったりして色々と調べてみると、特定の2 filesが先にあると失敗する様子。何それ。どちらかがない、もしくはどちらかより先に実行されれば上手く行く。もっと詳しく見てみると、具体的には、shallow
した直後のwrapper.html()
の中身が全然違うという。どうして? そんなとこどうにもなんないじゃん。...あぁ、ぃゃ、test対象Vue Classできちんとcomponentを定義すると(e.g.components: { 'el-button': Button, 'el-dialog': Dialog }
)warningが消えてcomponentとして見えるようになることがありますが(特にElementUIはel-...
とclass nameが合わないので...(- -;)、そういうわけでもなく。何せ2 filesが組み合わさるとtestにコケるというのが謎です。試しにダメにするspec fileを一つ消してから全体的にnpm run test:vue
してみると、またコケるので、その2 filesはat leastであってもっと他にも組み合わせの悪いのがあるということです。i18nかなぁ? 確かに一方はenで他方はjaでしたが、大して使ってないのでcomment outしても変わらないですし... localでは全体的に通しても通っている、CircleCI上でも単体や相性の悪いものの後でなければ動く、でも全体的に通すとtestに失敗する、というのはとても悔しく、厄介です。結局心折れて、componentが見つからなければtest codeをthroughするようにしました。端からdescribe.skip(...
よりはマシでしょう。それにしても何でやねん。大したtestしてるわけでもないclasses(methodがcallされたかどうかをassertしてる程度)に阻まれて、ちゃんと作ったspecのtest(条件がどうならどういう表示状態、input
要素をclickしたらどういう文言のdialogが出て、等)を実行できないというのは何とも残念でなりません。 - Vueのtest、ムズカシイです。child componentの扱い、ですね。何気なく使っているであろう
el-table
とかel-dialog
、そのままだとそんなcomponent知らないよとwarningが出まくります([Vue warn]: Unknown custom element: <el-table-column> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
)。wrapper
からはHTML Tagとして扱えるので最後はそうするのですけど、出来ればちゃんとcomponentとして扱いたいところです(でないとtable等でdataをloopさせて表示を作るcodeだった時のdata部分がそっくりないのでtest出来ない)。spec classでshallow
(ormount
)する時にstubs:
するのかと思ってた(前それで頑張った気がした)んですけどそうではなく、spec対象元classでちゃんと定義されていれば?、否、ElementUIなんかは丸っと読み込まれているのでいいのかなぁ?ともあれ、spec classでlocalVue.use(Table)
としたlocalVue
をshallow
(ormount
)時に読み込んでやる必要がありました。でそのcomponentをfind
して.props()
して中身をexpect
していくという流れですね。最初はconsole.log(wrapper.html())
で何が捕れるのかよく観察すると。でも上記のように、何かが変わるとすぐ取れるcomponentが変わるようなので、怖いです。その辺りの機微がよくわからないので... - Vue testにおけるtickの待ち方。
it(..., async () => { ... await Vue.nextTick(); ...});
2 ticks待たないと値が変わらないことも。 wrapper.findAll('.cell')
で取って来た値はElement Objectなので、見た目上は空でもexpect(cell).to.be.empty
とは出来ません。expect(cell.isEmpty()).to.be.true
- Vue componentって、初期値
props
とdata
は分けねばならない? 当初、data
に初期値を渡そうとしてどうしても出来ず、途方に暮れました。data
の初期値はdata
部に書いた最初に返す値でそれは何でもよく、tagの方で渡す値で上書きされるのかと思ったら違うんですね。ではdata
ではなくprops
を使うのかと思うとそうでもなく、props
を後から変更しようとすると怒られますし。Stackoverflowにあった解決策は、初期値props
とdata
を使い分けること。無駄な変数増えて何だかなぁとは思いますれど、そういうもの? slot-scope
って、="scope"
ではなく={row}
とやればいいんですね。本で知りました。...mapGetters
がなかなか効かなくて苦労しました。computed:
に入れる、storeのmodule構造に合わせてmodule(file)名/method名
にする、とやったんですが、undefined
って言われて。store
経由でthis.store.value_name
とかってやると取れるのに。でもState を直接参照せず、getter 経由で参照することを強く推奨するというので頑張りました。一回は諦めたんですけど、その後retryしたらうまくいくように。結局どうしてうまくようになったのか、決め手が何だったのかよく分からずじまいです。- 確認dialogを出すというんですが、非同期のJavascript/Vueにあってはこれが意外とムズカシイです。確認dialogというからには処理を止めなければなりません。よくやるように「flagで表示を制御」ではmain処理止まらないんですよね。そもそもsleepだとて簡単には行かないですよねJavascriptで。とても苦労しました。
async
/await
を導入してやっと止めました。要は「dialogを作っているところをnew Promise
でくるんでreturn
、それをawait
、しているfunctionをasync
」ですかね。 - dialogって自分で
$destory()
出来るんですね。生成と削除は外からやらないとダメかな、と思っていた(ので$emit
して親で受けてdialog.$destory()
してた)んですけど、実際生成は外から$mount()
呼ばないとならないですけど(created:
やmounted:
はcallbackですよね)、「ボタン押したら消滅」で自己$destroy()
は出来るんですねやってみたら。いちいち面倒くさいけどやってみないとわからんもんですね。 - dialogを出す必要に迫られたんですけど、よくあるようにdialogを予めDOMに仕込んでflagでappear/disposeとするには、2階層上のVue componentに書かないと、loopで大量生産されてしまうことが判明。Vue component間は親との対話だったら
$emit
と、戻しは何だ??を使えば出来ますが、2階層上は面倒です。情報渡すだけならまだしも、その後の処理を孫でやるならdialogの結果を2階層戻してもらわないとならないですし、じゃぁ処理もgrandparentに渡す? 処理に必要な情報も渡さないとだし、grandparentで知らなくてもいいことを知らなきゃいけないなんて。ということで、何かないかなーと探すと、揮発性dialogというものが(揮発性の高いコンポーネントを作る話)。探すと似たようなことしてる人割と多く。これを理解して、そのままではなく2 filesで済むようにして自分のにapplyするのも頭を使いました。これ結構いいような。Vueのdialogも一般的にこれでいいような。初からDOMについているのではなくって。 - 既述のように、
await
もうまく行ってよし、じゃぁtestを書くぞ、と試したら、いきなりわけのわからないerrorに見舞われてそもそもtestが動かなくなってしまいました。なんでだろー?! 自分以前では確かに動いていたので、自分のせいです。でもどのfileが悪いとかも出て来ないのでまるで取っ掛かりがなく、途方に暮れました。error messageで検索すると、どうやらなんとasync
/await
を使った場合に出るbable
/polyfill
系のerrorだとか。えー!! 折角苦労してasync
/await
でdialog止めたのに! 代替手段なんて思い付かないよー! ということで、何とか突破すべく頑張りました。babel
の線で色々と調べてみると、こうしたら直った、ああしたら、とか種々書いてあるんですね。新たなgem install以外は全部試しましたけど、うまく行かず。でもこの記事を参考にvueのsetup.js
冒頭にrequire("babel-polifill");
と書いたら!! 動いてくれました!! 省みれば僅か1行の追加ですけど、この1行には数時間もの汗と涙が詰まっているのです。 - APIの試験をするのに、
curl
やPostmanを使うのはよくありますけど、cookieが何か長くてcopyが面倒だなーと思ったので、Chrome Developer toolのConsoleにてfetch
でやる手法を。fetch(`/apis/endpoint/${id}`,{method:method,credentials:'include',headers:{'X-CSRF-Token':token}}).then((response)=>console.log(response))
とすればBrowserの持っている認証情報をそのまま使ってくれます。credentials:'include'
が肝です。どうせ使い捨てcommandですから。 - Railsは自動的にCSRF対策がなされている、ということで、何してるんだろーと思いきや、session毎に予測困難な
X-CSRF-Token
が付くというものでした。request毎に違うものでなくてもいいのか? いいのか...予測困難なら... - Railsの日付、Timezone気を付けないといけないのですけど(AWSはUTCなので)、
Time.current
やDate.current
のように.current
methodを使えばTimezoneを意識してくれる模様。.now
より.current
がということでやはり「ナウい」は死語ということですか。 - Ruby、共通処理は継承関係作って親クラスに、と思っていたんですけどそれってJava的発想? OOP的というか。RubyではむしろMixin? MixinってAOPかと思ってたんですけど。Duck Typingだからそもそもclass的には無関係でいいんですね。Java屋さんとしては何だかなぁと。
- Rubyのstream(
map
やselect
やreject
やeach
等)、もうfoolproofとしてlazy...force
を付けるのがいいんじゃないか、と思ったんですけど、そうでもないんですねこれ!! ってかそもそもどうしてlazy
がdefaultじゃないんだろう? ってJava屋さんとしては思ってたんですけど、銀の弾丸ではないという話があって衝撃でした何を今更ですけど(古い話のようで今は多少performance上がったそう)。というわけで、lazy
は「省メモリ」か「後でかなりの数take
することがわかっている場合」でないと効果なさそうなんですね。元ネタ - 最近何か安易に大量のlogをslackに投げていたので、そうじゃないでしょ基本的にはlog fileでしょ、それもdefaultのfileでいいよね、ということで、
Logger.new(...)
ってやったんですけどどうして毎回file nameを指定しなきゃいけないのか、しかもfile名指定してnew
すると都度file作りやがる、のでappend modeでopenして、とかってやらないとならないの?! なんか面倒臭くね? と思ってたら、Rails.logger
でいいんですね。なぁんだ。@logger ||= Rails.logger
controller
で最終的?に全てのerrorを拾うにはrescue_from error_class名, with: :method名
、事前validationを定義するにはbefore_action :method名
- 決まった名前をclass methodにするか定数にするか、は、定数の方が良いようです。ref. Class method vs constant in Ruby/Rails
- Rubyで「objectの同一性」とは、Javaと違って
==
、eql?
、hash
の3 methodあるんですね。うち==
はArrayで、残る2つはSetで使われる模様。Arrayでは比較通るのにSetでは違うと判定され悩みました。Setではobject hashを取っているからなんですね。でも結局、.to_json
すれば同一性methods定義しなくても行けました。何だかなぁですけどまぁunit testなのでいっかー。 - すっかり忘れてましたけど
routes
でresources
を使えばendpoint定義をまとめられますね。path parameterも積極的に使うべきでしょう(その存在を強制できるので)。parameter nameは基本id
ですけど変えられるんですね。 - ActiveRecordで、MySQLみたいに「あれば取得、なければ作る(recordも入れる)」なんてあるかなーと思ったら
.find_or_create_by
がありました。更にforeign key制約をつけて、その制約に反したらerrorにして欲しい場合は、.find_or_create_by!
でした。 - RubyでVO(Value Object)を作るのは、どうしたらいいんですかね。
object = Struct.new(:variables, ...)
とobjectとして使えば良い、という記述があったので、今度試してみます。←ここでいうobjectってclasなんですね。 - RubyにもJavadocのようなcomment流儀あるんですね。YARDですか。
@param [引数のクラス] 引数名 引数の説明
、@return [戻り値のクラス] 戻り値の説明
- 配列からhash mapを作る手法、色々ありますよね。よくある「
[key, value]
の配列の配列にしてから.to_set
」はloopを2回回すので何だかなぁと思っていました。Rubyにもあるreduce(inject)
を使えば出来る!とあったのでやったのですけど、そうしたらrubocopに「そういう時はeach_with_object
を使え」と言われました。ナルホド、それなら確かにblockの最後にhash
をreturnせずともよいわけですね! というわけで、list.each_with_object({}){|value,hash|hash[key]=value}
、更に一気にhashのhashを作るには、list.each_with_object(Hash.new{|hash,key|hash[key]={}){|value,hash|hash[key1][key2]=value}
- 同様に、一気に
Set
を作るには、list.each_with_object(Set.new){|value,set|set<<value}
の方が、最後に.to_set
よりよいでしょう。 - reekに「
log.debug
を複数回使っているからまとめよ」と言われて弱りました。単純にまとめると、毎回引数を評価しちゃうじゃないですか。でもloggingなので遅延評価が必要でしょう。というわけで、blockを残せるようにmethod定義しました。def debug(&block) Rails.logger.debug(&block); end
使う時はdebug{...}
です。 rescue
で=> e
とせずとも$!
でerrorを参照できるんですね。rescue
でretry
とすればbegin
からまたやってくれるんですね! ただ、上限設けないとずっとretryし続けるので注意ですかね。- Railsでmail送信は、configが終わっているのであとは、
ApplicationMailer
を継承したclassを作成、app/views/class名/method名.html.haml
といったtemplateを用意、という感じでした。html mailだけだとspam判定されやすいとのことなので、app/views/class名/method名.txt.erb
も用意しました。classのinstance fieldをmail template中で参照できます。 - ActiveRecordでの取得はなるべく
.pluck
にした方が速いし軽い、ということでそうしてますが、でもそうすると何のためのDTOなのかと。model定義の意義が少し薄れるような。modelがfatなのが悪い?.pluck
でない場合はせめて.select
で選択するfieldを減らしてやって生成されるobjectを少しでも軽くすると。ただActiveRecordからObjectを生成するcostが高いとのことなので、.pluck
の方が推奨されています。....select
って、modelにないfieldもselectできるんですね。そしてdynamicにmethodが生えると。なんかそこまで行くと気持ち悪い気がするのは、やはりJava出身だから?? yield
はよく使いました。if block_given?
も併せて。- instanceからclass variableへの参照は
instance.class::VARIABLE
でした。 - rspecで
allow(Rails).to receive(:logger).and_return(spy('logger'))
とするとloggingが一律空回しになります。 - method chainを
allow
するにはreceive_message_chain
(e.g.expect(SomeClass).to receive_message_chain(:joins, :distinct, :where, :pluck)
).with(...)
を複数書いても、それぞれのmethodの引数指定ではなく、最後の一つしか評価されていない? - rspecで、違ったparameterで同じobjectのmethodをcallするのをstubするには、
allow(...).to receive(:same_method).with(...)
を並列で何度も書けばよいです。with(...)
をmethod chainさせるとかではないんですね。 - rspecでinstance doubleに引数を連ねるとmethod stubになります。e.g.
double('user', id: 1)
→user.id==1
(double
の最初の引数は任意名) - 調べればすぐ出てくることではありますが、Railsのmigrationの
create_table
時にprimary keyを自動で出来るid
以外にしたい場合はcreate_table
の行にprimary_key: key名
と書きます。更にforeign key constraintを付け加えたい場合は、その下にadd_foreign_key :table名, :foreign_table名
と書けば大丈夫でした。 - RubyでTime format時、
.strftime('%X')
とすると一文字で15:37:21
に、.strftime('%F')
とすると2020-03-01
になるので便利です。また、.iso8601
とするとT
を挟んでTimezone付きで2020-03-01T15:39:10+09:00
というようにしてくれるのでこれも便利です。 - 嫁の顔忘れてもTimecop.returnは忘れないでねという話にあるように、
Timecop.travel(Time.parse('2020/3/1 0:01:03')) do ... end
で囲うのはいいですね。Timecop
も.travel
(特定時刻にしてから時を刻む)と.freeze
(特定時刻にして時を止める)では違うので注意です。 - Rubyで文字列の連結は
<<
がいいみたいですね。C++みたいですね。 - ActiveRecordでN+1問題を回避するには、なるべく
joins
した方が良いようです。が、図に乗って巨大tableをjoinするとそれはcostがでかいので、分割して引いた方がいいんでしょう。joins
するにはmodelにhas_many
とかの関係が書いてないとダメでした。2つ先の関連も書けて、has_many :second_table, through: :first_table
とthrough:
で繋げられました。 - と思ったのは幻想でした。書けちゃいますけど、joinされたものはkeyで結びついたものではなく、雑に合わさったもので、
where
で引いてもそうでないものまでついてきていて、bugを引き起こしていました。ちゃんとやらないとダメですね。 - 「ちゃんとやる」とは、Railsでモデルを4段階joinする方法で、もう一度理解するjoinsとmergeにあるように、ActiveRecordの
joins
で繋ぐ時にjoins(first:[second: :third])
とすること。これでちゃんとjoinされました。 - controllerで、APIっぽくheaderだけ返して中身は不要という場合、
head :created
(201)とかhead :unauthorized
(401)と書けます。 - Rubyのhere documentはもう
<<~
でよくないすか?<<
だと左端にひっつくので。 - Rubyの
reduce
はeach_with_object
に取って代わられるのですが、reduce(&:method)
で済むところに活路があるようです。使えたのはSet
のmergeですね。[Set.new([1,2,3]),Set.new([2,3,4])].reduce(&:merge)==Set.new([1,2,3,4])
- 複数回
.include?
するなら絶対Set
にすべきですよ。 - 定数文字列もなるべく
.freeze
すべきでしょうか。magic comment# frozen_string_literal: true
で(が?)よいのでしょうか。 - controllerのrspecで、
before_action
のtestを書くことになったのですが、色々と悩みました。どこに書くか? 書いたclass? 使うclass? Javaでabstract classのtestだとconcrete classに書きますが、controllerは使う側に書くと多岐にわたるので、application_controller_spec.rb
に書きました。 - curlでtest clientとしてJSONを
-d
でPOSTしたところ、どうもうまく行かなくて、調べるとserver側に"{\"key\":\"value\"}"
なんて渡っていました。どうしてー?! HTTP Request HeaderにContetnt-Type: application/json
が必要なんですね! そういえば。 - 今更ですがAngularJSのtestで、
window.location.href
をassertionしようとして四苦八苦しました。How to mock $window.location.replace in AngularJS unit test?等から得たことは、window.location.href
だとtest(jasmin)で拾えないので$window.location.href
にする、responseがemptyだとどうしてもtestの方でcatchしてくれないのでdummy値をresponse
代入する、testでは$window
がemptyで悩みました。$provide.value('$window',
でprovideしてbeforeEach(inject(function($injector, $rootScope, $httpBackend, $window) {
でinjectしつつwindow=$window;
と付け替えて、assertionのところでwindow
を使う、という。 diff
で相違は出ますが、共通行を抽出するには?comm -1 -2 file1 file2
(但しfilesはsort済であること) ref.2つのファイルの共通行を抽出する方法- MySQLでindexがついたかどうかを確認するには、
describe table名
ではMul
と付くだけでよくわからず。show create table table名
でCREATE文を出すことで。 - controllerのrspecで、sessionってどう表すのかと。
get :index,params:{aaa:'AA'}
だからget :index,params{...},session:{...}
かと思いきや、実際そう書いてあるところもあるんですが、それではうまく行かず、get :index,{param:'something'},{session:'something'}
と。ref. RSpec set session object - rspecでprivate variableにsetするには、
some_instance.instance_variable_set('variable_name', 'some value')
、getするには`someinstance.instancevariableget('variablename') - visitor patternを適用しようと思っていたのですけど、諦めました。RubyってDuck Typingだからあんまり効果を見出だせず。Ruby で Visitor パターンをあまり使わない理由,Ruby で Visitor パターンを使うときという投稿もあり。
upstart で start した job の設定を変更するには一度 stop する必要がある へーそうなんだ! ってぃぅか何のための
restart
っすか?restart
あるんだから機能すると思いますよねぇ...orz1.hour.ago
があるのに.later
や.after
はないのでどうするんだろう? と思ったら、1.hour.from_now
っていうんですね。controllerのrspec書いていて、やっと単体で通るようになったので、いざ全体的にrspecかけてみると、コケます。確かに自分の書き足したところ。でも通る時もある。どうして?→そういう、rspecの結果が不安定な時は、実行順序依存性があります。これを紐解かねばなりません。それが厄介でした。といいますか、rspecって全体的にかけると、といいますかspec fileを指定しないでdirectoryだけ指定して実行すると、spec filesの実行順序がrandomなんですね。それはその方がいいですね。なので、色んな手を使って順序依存性を解明していきます。ref. RSpecコマンドのオプションまとめ rspecに
--order random:NNNNN
を付けてrandom seedを固定、-f d
として出力formatにclass名を出すようにし、--no-color
でescape sequenceを抑制します。-o filename.log
でfileに出力、それをうまく行ったときと失敗した時で保存して、class名を比較しました。当該classのtest以前だけが大事なので、そこだけ出してsort
してdiff
取ります。失敗事例が複数取れれば、それらの共通classをcomm -1 -2 <(...) <(...)
で抽出するのが良いです。1 2 3 4
docker-compose exec rails bash -c 'RAILS_ENV=test bundle exec rspec --order random:32510 -f d -o 32510.log --no-color' diff <(grep -e '^[A-Za-z]' -e '^ Apis::' controllers2.log|awk '{if(/ApplicationController/){exit}print}'|sort) <(grep -e '^[A-Za-z]' -e '^ Apis::' 32510.log|awk '{if(/ApplicationController/){exit}print}'|sort)|ag '<' comm -1 -2 <(grep -e '^[A-Za-z]' -e '^ Apis::' controllers2.log|awk '{if(/ApplicationController/){exit}print}'|sort) <(grep -e '^[A-Za-z]' -e '^ Apis::' 4627.log|awk '{if(/ApplicationController/){exit}print}'|sort) comm -1 -2 <(grep -e '^[A-Za-z]' -e '^ Apis::' controllers2.log|awk '{if(/ApplicationController/){exit}print}'|sort) <(grep -e '^[A-Za-z]' -e '^ Apis::' 48327.log|awk '{if(/ApplicationController/){exit}print}'|sort)|grep -r -l -f /dev/stdin spec/controllers|sort -u|tr -d '\r'
単体では通るのに全体だと時々こけるというのはどうしてもよくわからないので、何度も何度も実行して観察してみると、コケる時のFの出る位置がまちまちなんですね。あぁ、これって毎回test順序違うのか、とその時初めて思い至りました。それで、rspecにおけるrandom seedの固定方法や出力形式の指定の仕方を調べ、何とか実行順を成功時と失敗時で比較して、怪しいものを試して、辿り着いたのでした。
controllerのtestでは、
before_action
に指定したprivate methodのtest且つ利用するchild classでのtestではなくbase classでのtestなので、当初はcontorller.send('method_name')
とかやってたんですけどこれは本来的ではないな、ということで、Anonymous Controller使いました。といってもdescribe
の中でcontroller do ... end
して、そこでbefore_action :method_name
しただけのものです。それでそのdescribe
内のcontrollerは定義したものになるという、お手軽な。でもここでエラー(Error
をthrow、じゃないRubyだからraiseですか、する筈が正常に終了してしまう)が出て、悩みました。結局、わかってみればどうということはないのですが、他のspec classで、ApplicationController.skip_before_filter :login_required
をbefore do...
でやっていて、でもafter do...
で元に戻していなかったから、というのが原因でした。そもそも、
ApplicationController.skip_before_filter
なんてせずに、単にallow(controller).to receive(:method)
とmockすれば十分です。そうしたらafter do...
だって不要ですし。ref. before_filterを分離してテストする方法(Rspec) 別段true
返す必要もないんですねこのbefore_action
って。before_filter
は古いんだそう。いつも
schedule
の確認をするのに。bundle exec whenever
AngularJSでのredirect? になるのかな? は、routesの
$stateProvider.state('...')
に定義されたところへ、$state.go('...')
で飛びます。ref. Angularjs UI Routerの使い方 でも結局、AngularJSのrouting範囲外に飛ばしたかったので、$window.location.href = ...
で飛ばしました。ActiveRecordの
scope
に引数を取る場合には、scope scope_name ->(arg1, arg2) { ... }
矢印とカッコはくっつけないとreekに怒られます。Rubyで
String
のselect
を正規表現でするならgrep(/.../)
、-v
の場合はgrep_v
(Ruby equivalent to grep -v)AngularJSのroutingってURLについたanchor(
#
以下)に出るのですが、それってそもそもbrowserがserver側に送信しないんですね...ActiveRecordで選択して保存を1行で。例えば
User.find(1).update(name:'new name')
rspecでActiveRecordのtest dataってfixtureやFactoryGirlを使うものかと思っていたのですけど、普通に
.create
とか.update
とかして大丈夫なんですね。大丈夫、というのは、後腐れがない、んですね。RSpecで
allow
でmockした時に、.and_return
だけでなく処理(sleep(1)
)を入れたかったんですけどどうしたら? と思ったら、単にblockでいいんですね。blockの最終評価値がreturnになる、というなら.and_return
って不要なの?
Add Swap File
年末にideapad 310のmemoryが壊れてしまい、2度買い直してもmemoryを認識しなかったので、これは本体側が壊れたのだろうと。これを直すには本体のmother board交換になる、くらいならまだ2年程しか使ってませんが新しいのを買う? と考え、物色していたところ、新しいものでもmemoryって搭載容量4GBとか程度で、何故? と思ったんですけど、今はSSDだからそれでいいってことなんですね! そっか、今はSSDが廉価になったからswapでいいのか! なら310もSSDに換装すれば、ということで、SSD 1TBにしました。1.11万円でSamsungの1TBをGET。ubuntu 19.10を入れて... 最初、折角だからZFSにしてみたのですが、SSDと相性悪いんですねこれ。swap partitionは、位置が固定されるためSSDには良くないと。XFSもSSDではjournalingを止めた方が良いとのことで、それだと何のためのXFSなの?! ということなのでつまらないですけどext4で再installします。しかし、partitioningをお任せにすると、今はdefaultでswap partitionではなく2GBのswap fileになるというのは驚きですね。 ということで、本題です。よく、dd
でswap fileを作る例が載っていますけれども、スワップ領域の追加方法|server-memo.netにはfallocate
を使う方法があったので、メモです。
1 2 3 4 5 6 |
|
Google Photo Api
一日千枚とか写真撮る人だと写真がすぐ溜まっちゃうんですよね。 backupは無限のGoogle Photosに、ということで、前はPicasaのAPI、upload_gphots
を使ってたんですけど、もう無くなっちゃっていて。どうしよう、途方に暮れていました。暫くぶりに探すと、丁度1年程前からGoogle Photo APIが整備されたようで、良かったです。ずっと待っていました。 [追記あり] Google Photos APIsでアルバム作成と写真のアップロードとGoogle Photoを業務システムのクラウドストレージとして使った結果、本家API Documentを参考に早速使ってみます。
ACCESS_TOKENの取得
- APIの有効化
- Google Developer Consoleから「認証情報」→「OAuth2.0クライアントID」無ければ上の「認証情報を作成」pulldown menuから「OAuthクライアントID」(「ウェブアプリケーションの種類」は「その他」)で作成
- 上記「クライアントID」「クライアント シークレット」をメモ
- 次のURLに
$CLIENT_ID
を入れてbrowserでaccess、AUTHORIZATION_CODEを取得 (https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=$CLIENT_ID&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope=https://www.googleapis.com/auth/photoslibrary&access_type=offline
(SCOPEはGoogle PhotoでのR/W accessの場合はhttps://www.googleapis.com/auth/photoslibrary
) - 以下のようにして、
ACCESS_TOKEN
及びREFRESH_TOKEN
を得る1 2 3 4 5 6 7 8 9 10 11 12 13
$ AUTHORIZATION_CODE=4/wnmGpTh__1zdrgdjmPWyetUI7C1mvsjRrA_IyZmwY7aSeYppD9X_9iB $ CLIENT_ID=952391557281-s8b8ditnocfu590fi0ntsfk76rbmkm80.apps.googleusercontent.com $ CLIENT_SECRET=k6XPLuryMWUtKDKmS1cYgW0r $ REDIRECT_URI=urn:ietf:wg:oauth:2.0:oob $ curl --data "code=$AUTHORIZATION_CODE" --data "client_id=$CLIENT_ID" --data "client_secret=$CLIENT_SECRET" --data "redirect_uri=$REDIRECT_URI" --data "grant_type=authorization_code" --data "access_type=offline" https://www.googleapis.com/oauth2/v4/token { "access_token": "ya29.GlsOB-ebr6NrI78UemOPHcm1-jdw0XkxD8iiSqE-Bh5xB_Sx8bhKsRhRyz7gqJy45A-HIF6s6GF0j5wz0dmNppVqEMhtUurAwfbe-xgEsR5MZFjoIY3ONOx8zd4Q", "expires_in": 3600, "refresh_token": "1/8LrGRLdBaFJYHlOr0rEAyZcgC9yDl2PcZZyrbqoxc7c", "scope": "https://www.googleapis.com/auth/photoslibrary", "token_type": "Bearer" }
ACCESS_TOKEN
は1時間しか有効でないので、適宜REFRESH_TOKEN
を使って更新1 2 3 4 5
$ REFRESH_TOKEN=1/8LrGRLdBaFJYHlOr0rEAyZcgC9yDl2PcZZyrbqoxc7c $ CLIENT_ID=952391557281-s8b8ditnocfu590fi0ntsfk76rbmkm80.apps.googleusercontent.com $ CLIENT_SECRET=k6XPLuryMWUtKDKmS1cYgW0r $ ACCESS_TOKEN=`curl -s --data "refresh_token=$REFRESH_TOKEN" --data "client_id=$CLIENT_ID" --data "client_secret=$CLIENT_SECRET" --data "grant_type=refresh_token" https://www.googleapis.com/oauth2/v4/token|jq .access_token -r`
REFRESH_TOKEN
を取得すれば、あとCLIENT_ID
とCLIENT_SECRET
が分かればACCESS_TOKEN
は更新できます。
ALBUMの作成
- 既存のAlbumの確認
1
$ curl -s -H "Authorization: Bearer $ACCESS_TOKEN" https://photoslibrary.googleapis.com/v1/albums?pageSize=50
- 既存のAlbumの確認(
nextPageToken
がある場合)1
$ curl -s -H "Authorization: Bearer $ACCESS_TOKEN" https://photoslibrary.googleapis.com/v1/albums?pageSize=50&pageToken=...
- 新規Albumの作成
1 2 3 4 5 6 7 8
$ DIR=20190428 $ curl -s -X POST -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-type: application/json" -d '{ "album": { "title":"'$DIR'" } }' https://photoslibrary.googleapis.com/v1/albums { "id": "ADIlBkAOcfB64a_Opnwdjgxeq6jhQv4GQ1pZQ-wse2o2hiBIofuhefmFycfTtIcLAG0inLt0FlZn", "title": "20190428", "productUrl": "https://photos.google.com/lr/album/ADIlBkAOcfB64a_Opnwdjgxeq6jhQv4GQ1pZQ-wse2o2hiBIofuhefmFycfTtIcLAG0inLt0FlZn", "isWriteable": true }
UPLOAD and adding to Album
2段階になっていて、
- binary fileをuploadして
UPLOAD_TOKEN
を得る UPLOAD_TOKEN
を元にmediaItems:batchCreate
する(ALBUM名はここで渡す。batch処理なので複数のUPLOAD_TOKEN
を渡せる)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
folderまるっとupload
- 事前準備
1 2 3 4
REFRESH_TOKEN=... CLIENT_ID=... CLIENT_SECRET=... ACCESS_TOKEN=`curl -s --data "refresh_token=$REFRESH_TOKEN" --data "client_id=$CLIENT_ID" --data "client_secret=$CLIENT_SECRET" --data "grant_type=refresh_token" https://www.googleapis.com/oauth2/v4/token|jq .access_token -r`
- Album作成
1 2
DIR=... ALBUM_ID=`curl -s -X POST -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-type: application/json" -d '{"album":{"title":"'$DIR'"}}' https://photoslibrary.googleapis.com/v1/albums|jq -r .id`
~/photo/$DIR
以下のimg_*.jpg
filesのuploadとalbum登録(約100 files毎にACCESS_TOKEN
のrefresh)
1
|
|
uploadに失敗したfile namesが標準出力と/tmp/upload_failed.log
に出てくるので、後刻それらをretry。
1
|
|
これではbatch処理を活かしていない(複数のUPLOAD_TOKEN
をbatchCreateしていない)のですが、Googleだけに割とすぐ終わること、error handlingがあまりにも複雑になることから、都度batchCreateすることにしました。
私の場合、1000 filesで約3GB弱、を目処に分割してuploadしています。 uploadしたfilesは全て「元のサイズ」で保存されてしまい、Google Driveの容量を消費してしまうので、設定から「容量を解放」しなければなりません。これが「1日1回」となっているものの、だからといって24時間後に再度実行しても「ファイルを圧縮できませんでした。ストレージを復元できるのは 1 日 1 回だけです。」と言われて出来ず、困っています。実際に再度実行できるまでには1.5日〜2日かかるようで、これが最大のneckになっています。
新規Albumへの既存files追加
これはダメでした。 何度試してもダメだったので、調べてみると、公式Documentに、 Note that you can only add media items that have been uploaded by your application to albums that your application has created.とあります。 なんでやねん! 何で既存の画像とAPI経由の画像とを区別するのか、わけわかりません。 それじゃぁ、っていうんで、既にGoogle Photos上にある写真も改めてuploadしてalbumにaddしたら、それは出来ました。しかし、「元のサイズ」になってしまって容量を食ってしまいます。これについても「容量を解放」しなければなりません。 全く七面倒臭いものです。
ちなみに、以下のようにやりました。paginationが発生しない程度のAlbum限定で、 1
2
3
4
5
6
7
8
9
10
11
12
13
$ DIR=20171005
$ ALBUM_ID=`curl -s -X POST -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-type: application/json" -d '{ "album": { "title":"'$DIR'" } }' https://photoslibrary.googleapis.com/v1/albums|jq -r .id`
$ MEDIA_ITEMS=`curl -s -X POST -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-type: application/json" -d '{"pageSize":"100","filters":{"dateFilter":{"dates":[{"year":2017,"month":10,"day":5}]}}}' https://photoslibrary.googleapis.com/v1/mediaItems:search|jq .mediaItems[].id|sed -z 's/\n/,/g'`
$ curl -s -X POST -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-type: application/json" -d '{"mediaItemIds":['$MEDIA_ITEMS']}' https://photoslibrary.googleapis.com/v1/albums/${ALBUM_ID}:batchAddMediaItems
{
"error": {
"code": 400,
"message": "Request contains an invalid media item id.",
"status": "INVALID_ARGUMENT"
}
}
Album中の全file名取得
自己解決しました。 NEXT_PAGE_TOKEN
あると面倒くさいんですけど、これで何とか。
Album探し
最初のpageに目的のalbumがあるかをこれ↓で探す
1
|
|
1
|
|
見付かれば、ALBUM_ID
を同定。
1 2 3 4 |
|
そうしてから徐に、 1
2
ALBUM_ID=...
NEXT_PAGE_TOKEN=`curl -s -X POST -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-type: application/json" -d '{"pageSize":"100","albumId":"'$ALBUM_ID'"}' https://photoslibrary.googleapis.com/v1/mediaItems:search|jq -r '.mediaItems[].filename,.nextPageToken'|tee /tmp/files.txt|tail -1`;while [ "$NEXT_PAGE_TOKEN" != null ];do NEXT_PAGE_TOKEN=`curl -s -X POST -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-type: application/json" -d '{"pageSize":"100","albumId":"'$ALBUM_ID'","pageToken":"'$NEXT_PAGE_TOKEN'"}' https://photoslibrary.googleapis.com/v1/mediaItems:search|jq -r '.mediaItems[].filename,.nextPageToken'|tee -a /tmp/files.txt|tail -1`;done
その後、
1
|
|
で取り出せます。
それで比較(diff -y --suppress-common-lines <(cd ~/photo;ls .../img_*|sort) <(grep -vE -e '.{300,}' -e null /tmp/files.txt|sort)
)したところ、足りないものはわかりました。 ですが、何故かAPIで取ると3282個なのに 「コンテンツ 3283個」と表示されていたり... よく精査すると、なるほど、Google Photosが勝手に?作った、 アシスタントにある MOVIE.mp4
(ムービー)や...-EFFECTS.jpg
(スタイルを適用した写真)、 ...-PANO.jpg
(パノラマ)が含まれているから? いや、一つ(5230個と表示)はそれが原因で9個多く数が表示されていたのですが、 もう一つ、「コンテンツ 3283個」は、APIで取得するといくら見ても 3282個しかない、自動生成物もない、です。謎です。
それと、EnrichmentとかいってTextやLocationとMapを入れられるんですけど、 それらを取得する術がなく、Textに書いたことを検索するとかも出来ず。 GUIから入れてみましたが、要素を追加する度にいちいち先頭に戻される、 移動すると他の要素もどこかに行ってしまうことがある、 等何だかなぁ、というものでした。 Googleならこんなもんじゃないだろー!
3 Good Things in This Week
また幸せになるため?に、 先週3つ良かったことを並べてみます。
- バイトで発表を何とか凌げたこと(でも本番のreportを書き上げる筈のこの土日、またなぁんにもしませんでした...orz)
- 職場で定期本番リリースを無事?終えたこと(完全に付きっきりでしたけど。まぁ最初だから仕方ありませんよね。けど次のリリース、一人で出来るかな?)
- Paiza S Rank、CodeIQ S Rankを持っていたこと(でもこんな役に立たないもの自慢?という程のものではないにしても、何だか、ですが。みみっちぃなぁ...)
あと、12月半月分の給料もいっぺんに出たので、 今回の総額が多かったこと、とか?
良くないことは沢山思い付くんですけど、 それじゃぁいけないんですよね。 しかしまぁ、総体的には健気に生きています。