20190927のRubyに関する記事は13件です。

4&5/100日目 #100DaysOfCode #qiita #駆け出しエンジニアと繋がりたい #プログラミング初心者

やる予定だったもの

progate rails9-11 読み込み
progate rails2章 手を動かす

やったこと

progate rails9-11章 読み込み
progate ruby 1-3章 読み込み
html&Css 初級と中級 読み込み

学習時間

2h, 1.5h

反省・改善点、気づき

朝方にしてシフトして来た。

なお課題の実機操作については0-7時の睡眠なので、23-6時に来週以降は固定して対応する。

progateの活用方法をシェアしたところ意外と反響があったため、情報のアウトプットは重要だと実感。
https://twitter.com/kakedashi_555/status/1176120891849068545?s=21

明日やること

atomインストール等環境構築
rails2-3章 実機
予定見直し

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プログラミングスクールに50万溶かしたリーマンがエッッサイトを作った話


どうも29歳中小企業営業マンです。

 

プログラミングスクールに50万を費やして挫折→復活→挫折→その後5ヶ月でサイトをイチから完成させました。ここまで本当に長くて長くて大変だったよ・・・。サイトを完成させて本当に泣きそうなほど嬉しいよ・・・。辛かった・・・。本当にお兄さん辛かったよぉッッ


実は、ここまで来るのにプログラミング学習で50万使いました・・・。


著者歴
年齢: 29歳
仕事: 営業職(B to C)
学歴: 国公立大学 文系(経済学部)
プログラミング歴: 10ヶ月
開発期間: 5ヶ月(1日2時間くらい)
はじめてPCを使ったのは? :高校生、Windows95のメイプルストーリーがやりたくてはじめて触れました。


♡うそです、中学3年生のyourfilehostで動画鑑賞しますた♡


そこで作りました。

http://shiko-tsuma.club

 


シコい人妻のコンセプト



ひとことで表すと人妻サイトです。
機能は極力シンプルにして、ユーザーが見やすいようなデザインを第一優先にしました。(もちろん管理人も日々
シコい人妻http://shiko-tsuma.club
をシコシコ使ってカイゼンしています。)


すべて、と人気(PV数で並び替え)のメニューがあります。
さらに、投稿時に付与したタグをクリックすると同じタグの付いた動画が一覧表示されます。


 


サイトデザインについて


動画サイトのキングをインスパイアしました。そのサイトはとてもシコりやすいデザインなので別の意味でもいつもお世話になってます。
フォントはGoogle Fontを使用しています。また色合いはごちゃごちゃしないようにメインカラー(黒)、ベースカラー(白)、アクセントカラー(ピンク)以外の色は使用していません。
あともちろんスマホレイアウトを最優先しました。そしてタブレットのデザインは省きました。



開発までの経緯


実は、一回プログラミングに挫折しております。
動画で学べるプログラミング学習サイトをすべて学んだらHサイトを作れるんじゃね?って思った時期がありました。
ちょうどこの記事も参考にしていましたし→https://anond.hatelabo.jp/20101203150748
動画サイトでHTML, CSS, PHP, JavaScriptのメニューを3ヶ月位でこなしました。もちろん有料会員。(3ヶ月で3000円を費やす)
しかし、PHPのどうしても解決できないエラーにハマりました。
「クソッ、なんで動かねぇーんだ、チクショウ」そうやって机を叩いたことがたくさんあります。
「もうやめよう、無理だ、いや、まだ続けよう、いや、やめよう、無理だ、いや、まだ続けよう」そうやって何度も悩み続けました。



どうしてもプログラミングでサイトを作りたい!
けど、どうしようもない!
どうすればいいのか悩んだ挙げ句、Google先生に頼みました。



プログラミング教材、プログラミングオンラインスクールについて


プログラミング Web開発 などで調べると魅力的なページに辿り着きました。プログラミングを教えていて、サロンなどもやっている○○○さんという方のBlogでした。
そのBlogでは



プログラミングで自由な生き方ができる!
プログラミングって魔法なんだ!
こんな夢のようなキャッチフレーズに心打たれました。


本気になった私は教材の購入ボタンを押しました。



某○○○さんの教材



ものすごく期待して届いた教材を進めたところ・・・



「なんだこれ、本で読んだのと全く同じやん・・・」
「こんな基礎中の基礎なんて」



基礎中の基礎しか学べませんでした。
HTML、Ruby、Railsの基礎中の基礎なのでプログラミングを初めてする人にはいいかもしれないです。けど自分が実現したいWeb開発には直接は役に立ちません。もはや悲しみに近い怒りですがこの人は自分でWebアプリなどを本番環境やサーバを立てて自分で運営したことがあるのだろうか?と怒りを禁じえませんでした。
(後にエンジニアのメンターさんは20年前からまるで成長していない(安西先生風)Webアプリケーションを作らせている、と言っていました・・・。)
(ここまでで10万円を費やす)



「ん~、基礎ばっかりやってもまだ掲示板しか作れないし、作りたい機能や動かないコードの質問をしても調べろ、って言われるだけだったしな・・・」
「本当にプロのエンジニアに教わったら早いかもしれない」
そう思いました。



そして、「プログラミング 実践 開発」 「プログラミング エンジニア 講師」で調べると現役のエンジニアがメンターとして週に何回か見てくれるプログラミングスクールにたどり着きました。


 


オンラインスクールで学ぶ



そこで行われるカリキュラムというのは、オンラインスクール自身が作成した教材を進めながら、エンジニアのメンタリングを週に何回か受けるというもの。
他のスクールも検討しながら、1ヶ月近く悩み続けました。しかしプログラミングは投資だ、と思い
「えい、40万円入金ッッ」(ここまでで50万円・・・勇気がいりました)を実行しました。



最初は休日の朝にメンターとのコンタクトがあったのですが、起きられないことが多々ありました(連日深夜まで残業だったからとても眠かった)。出された課題も1日2時間もかかるので仕事で神経を減らして疲れた体には辛かったです。
「正直仕事が忙しいし、プログラミング勉強してる暇ないから、また挫折か・・・」と思いました。めちゃくちゃダメ人間なのです。



「また何も成し遂げられないのか・・・」



実は私、大学受験で第一志望に落ちて、理系に行くはずだったのに文系で絶望しました。だから理系のプログラマーに憧れます!
やりたかった仕事の就職も失敗しました。
高校も不登校だったし、昔うつ病も発症しています。



「もともとダメ人間でクズな自分が継続などできないのか・・・」



しかし、縁があって担当してもらったメンターさんの話が自分にとって刺激的で面白かったのです。


 


「Webサイトを大企業に売却して3000万稼いだ話」
「ベンチャー企業に誘われて手取り100万稼いだ話」
「どこどこのサイトのデータベースの裏話」
「自分の開発したサイトアプリケーションの話」


「すげぇ」そう思いました。
また、自分が仕事で使用しているツールがどうやって作られているのかも説明してくれてすごいリアルで面白かった。こちらの質問にも具体的に答えてくれて「すげぇ」って感じでした。
あと、オンラインスクールの推奨フレームワークがRubyだったのですが、Rubyが直感的で書いていて気持ちが良かったのです。



今まで動画サイトや本でフワッと学んだ知識がつながるのを実感しました。
気づいたら仕事から帰ってスーツのままプログラミングを学習していました。すごく楽しくなってきたのです。



しかし、シコい人妻http://shiko-tsuma.club を公開した今だからわかるのですが、プログラミングスクールの教材はRailsガイドのマネごとでした・・・。



この気持わかる人いますか?



ぶっちゃけ今だったら最初から


RubyとRailsの学習ガイド2019年版
https://magazine.rubyist.net/articles/0059/0059-Ruby-Rails-Beginners-Guide.html



の通りやったほうが良かった。



また、教材以外にもこれらを学びました。
https://www.sbcr.jp/product/4797386295/


https://gihyo.jp/book/2017/978-4-7741-9397-7


http://gihyo.jp/magazine/wdpress/plus/978-4-7741-4204-3


https://saruwakakun.com/html-css/basic



あとgitも公式チュートリアルで学びました
https://try.github.io/



いやー、オンラインスクールはgitの実践的なエラー解決は教えてくれなかったw
基本git -f はしてはいけないとか知らなかった。
おかげでgitミスって書いたコードが消し飛んだことが多々あります。


結局のところ、プログラミングの基礎的な内容については高額な教材(1万円以上)を買う必要はないかと思います。3000円の本を2,3周すれば十分です。高額な教材と3000円の本の内容は一緒です。(経験者は語る)1人では続かなくて誰かにサポートしてほしい場合は買ってもいいんじゃないでしょうか。
つまり、オンラインのプログラミング学習は教材にあまり価値はありません。そのサポートに数万円の価値があります。(人件費だから高いのは当たり前)



プログラミング学習が流行っていますが、プログラミングは手段です。何を目的にするかが重要かなと思いますね。私の場合はWeb開発でした。そして、Web開発において、大事なのは継続できるモチベーションづくりです。もし何度も挫折するのならば、良質な本で学びながらプログラミングを仕事にしているメンターに週何回か見てもらえばいいのではないでしょうか。



ぶっちゃけ、自分でサービスを開発していない人やレベルの低いエンジニアにわざわざ教わる価値はない、ですね。



シコい人妻ができるまで


プログラミング業務未経験の初心者がシコい人妻http://shiko-tsuma.club
みたいなサイトを作る場合、勉強したリストは以下の通り。
実際、私はこれらを10ヶ月くらいで開発しながらこなしました。
(途中別のWebアプリを作ろうとしたが続かず失敗、その後シコ妻に変更して5ヶ月後で完成して今に至る)



HTML / CSS


「HTML / CSSなんて常識だろ、パパっと学んどけばいいんだ」そう思ってあとで苦労しました。
これらの知識が中途半端だったので、いざゼロから書けってなったときに困りました。
たとえばRailsでリンクを生成するメソッドでタグの位置を微妙に間違えたりして表示されないことなどがありました。



Linuxのターミナル


基礎的なコマンドを動画で学びました。
https://dotinstall.com/lessons/basic_unix_v2



HTTPのしくみ


本でサラッと学びました。この本です。
https://www.amazon.co.jp/Webを支える技術-HTTP、URI、HTML、そしてREST-WEB-PRESS-plus/dp/4774142042/ref=sr_1_1?__mk_ja_JP=カタカナ&keywords=http&qid=1569577311&s=books&sr=1-1


とりあえずCRUDということを学びました(小並感)


Ruby


each, ifなどの文法を学びました。この本です。
https://www.amazon.co.jp/プロを目指す人のためのRuby入門-言語仕様からテスト駆動開発・デバッグ技法まで-Software-Design-plusシリーズ/dp/4774193976/ref=sr_1_1?__mk_ja_JP=カタカナ&keywords=ruby&qid=1569577335&s=books&sr=1-1



本当はオブジェクト指向とかできればいいのでけれどまだそのレベルではないです。Rubyは$とか書かなくていいので書いていて気持ちがいいです。でもプログラミング強い人からするとRubyは型が曖昧で気持ち悪いらしい・・・。本当でしょうか?



Railsについて


最初はフレームワークの何がいいのかわかりませんでした。ですが、Railsで開発したり、Railsの記事を読んだりするうちに、そもそもフレームワークとはWeb作りなどで使う定番のコードをあらかじめ用意しておいたもので、フレームワークをそのルール通りに使うと開発の時間が削減できる、ということを知りました。
今、オンラインスクールとかネット上で教えている内容の多くはRailsです。全くのプログラミング素人にRailsを教えているわけです。
しかし、初心者がいきなりフレームワークを使用してコピペで書いたツギハギ開発をすると「わけのわからないエラー」が量産します。
かくいう私がまさにそうでした。



参考
今すぐ辞めて欲しい、「Ruby on Rails勉強してます」「CakePHP勉強してます」
https://blog.sumyapp.com/2013/07/no-recommend-rails/



Railsは基礎的な文法を覚えるだけで簡単な機能を作れますが、一歩ルールから外れたことをすると途端にどうしていいかわからなくなり混乱します。
これからはサーバ、データベース、コンピュータサイエンスの基礎を学ぼうと思いました。


 


データベース


基本的なSQL文法などは動画サイトなどで学びました。あとは適宜ググりながらやりました。
また、1対多や多対多の使用例を開発して身にしみてわかりました。データベースは正規化、N+1問題、高速化などなかなか奥が深い分野です。ちなみにオンラインスクールはSQL文しか教えてくれませんでした。


 


サイトのコンセプトを考える


「ここまで来たらいよいよサイトづくりだ!」そう思い、興奮しながらサイト案を練りました。



ER図、サイトマップを作成する


「いーあーる図!?なんじゃそれは!」
ググるとどうやらデータベースの概要図とのことでした。そもそもデータベースに格納するデータはどのようなものか?そのカラムにどんな値を持たせるのか?関連は?ということを図にするのです。
参考 https://qiita.com/inexp_eng4432/items/817db9ba18829e0f6d57
(いつもQiitaさんお世話になってヤース)



あと、サイトURLからどのような構造をしているか、というサイトマップも作成しました。


trelloでタスクを並べる


「なにをやれないいのか、イマイチわかりづらいな・・・どうしよう」
https://trello.com
ここでタスクを書き並べました。付箋みたいで便利。


rails new rails migration


「rails new shikoi_hitozuma てぇぇーい!」
ER図で作成したデータベース構造を見ながら実際のデータベースにテーブルを構築していきました。


modelを書いていく


「まずはModelからだ、と習ったぜぃ」
リソースを人気順にしたり、新しい順にしたり、ということを書いていきました。


routingを書く


「index show だったな、これらをそれぞれのModelに関して設計していくぜぃ」
どういうURL階層にするか、というサイトマップを参考にして書きました。


controllerを書く


「次はControllerだな、Modelから渡されたものを加工するぜぃ」
index showなどのアクションごとにインスタンス変数を書いていきます。


viewを書く


「よし、今度は表示させていくぜぃ HTMLてててーいッッ」
実際に見る画面を構築していきます。


activeadminで投稿する


rails のgem にactiveadminという、管理画面を一瞬で作るものがあります。それを導入して投稿を管理しました。
「だけど、公式情報が少なすぎてわからねぇーーーーーーひぃいいいいい」
activeadminはものすごい苦労しました・・・。


サイトデザインをする


「CSSぅー?こんなんぱぱっと終わらすぜぇ」
はじめはCSSをバカにしていましたが、本気でやりました。パッと見のデザインで印象は大きく変わりますから。1日2時間で1ヶ月位かけました。
ちなみにエンジニアの人は「CSSなんて鼻くそほじりながら書けるでしょ」って思っているようですが、違いますよ。確かに思考はあまりいらないかもしれないけれど、最優先事項の一つだと思います。



本番サーバーを構築する


「くそーーーーーーー!クソクソクソ!!!動かない!どうしてうごかねーんだ、なんでだ馬鹿野郎!!」
ってめちゃくちゃ思いながら行った、本番サーバー構築。
今回は開発環境をバーチャル環境のUbuntuで構築しました。オンラインスクールでは某エディタを使用するのですが、メンターから「本当はこんなクソエディタの開発環境じゃなくて、ローカル環境でDockerとかを用いて開発できればいいんですけど」と言っていたのでローカル環境で開発しました。※クソエディタはCloud9です。


まぁ、開発環境の構築は問題ないとして、本番環境が色々難しかったです。VPSとかAWSとかそこらへんです。
※ちなみに今はDockerを勉強しています。環境構築がもう少し楽になるといいんですが・・・仮想環境とDockerの違いがわからない、なんであんなにDockerなんて流行っているの??疑問だ。


あと、そもそも開発環境で作ったものを本番環境にあげるなんてやったことなかったのですよ。だから開発環境と本番環境でgemを分けたりということを初めて学びました。
ここ、めっちゃ重要だと思うのです。Herokuで済ましている場合ではないです。
ちなみにオンラインスクールなどの教材はそのことについて触れてすらいないのです。本当の本番環境構築は初心者には難しすぎる、と認識しているのでしょうか?


 


使用技術について


Ruby
Rails


CSS



オンラインスクールではbootstrapを推奨していましたが、ちゃんとしたデザインで開発するならばbootstrapは使ってはいけないとのこと。だからFlexboxでイチからデザインしました。


 


gem


activeadmin
オシャレな管理画面。
これで記事を投稿しています。
投稿しやすいようにUIを変えるのに非常に苦労しました。情報が少ないのですw
苦労した点を書いていきます。



サムネイル画像をradioボタンで表示するには
動画を投稿する際に画像を見ながら選びたいですよね。
ラジオボックスのボタンを画像にする



activeadminのformのところをこんな風にします。
f.input :movie_thumbnail, as:
:radio,collection:Model.find(params[:id]).movie_thumbnail.select(:movie_thumbnail_url, :id).map { |c| ["<img src='#{c.movie_thumbnail_url}'>".html_safe, c.id] }


collectionのところでまずサムネイルを一覧表示させます。
そしてhtml_safeでHTMLタグとして認識すると画像が表示されます。


[Rails]ERBのエスケープを自在に扱おうぜ
https://qiita.com/satoken0417/items/7bcdb59eae82776375f9



kaminari


オシャレなページング(まだ入れてなかった)


 


pry



irbのオシャレなやつ
デバッグするときにbinding.pryするとirbよりオサレです。


 


サーバ



Nginx
unicorn
「nginx unicorn 早い、すごいお」
オンラインスクールの教材では一切教えてくれなかったですが、ここも超大事です。Webアプリケーションエンジニアのプロは高速なサーバーを構築するのが当たり前だそうですね。
これらは設定をググって書けばいいだけでしたが最初はハマって大変でした。
これらの記事でなんとか解決しました。
nginx + unicorn + Railsの設定方法
https://qiita.com/akito1986/items/56198edcafc222b320a8


STI


シコい人妻http://shiko-tsuma.club/には tagテーブルがあるのですが、そこから派生する妻ラベル(人妻の分類)や体型などのテーブルは内容が重複するのでSTIという設計を用いました。


参考記事
[Rails] STI(単一テーブル継承)とメタプログラミングでDRY
https://qiita.com/kidach1/items/789c2e7aebbcfbd2583e


これからは以下の機能を作っていきます。昼間はブラック企業で働いている関係上、作業は深夜になりそうですが、えっちい人妻のためにがんばります。
みんな、はてブコメントでコメントどしどしくれよな!


追加したい機能


 


セッションを利用した履歴表示


多分セッションを期間で区切ってやればできると思います。


ユーザーが投稿できるForm


ユーザーがこれを共有したいと思う動画のURLを貼り付ければ投稿できる、というやつ。activeadminでできますね。


コメント機能


commentというテーブルを作って、記事にリレーションさせればいいと思います。


ユーザーのお気に入り登録


これもfavoriteというテーブルを作ってuserにリレーションさせます。


日毎、週毎、月ごとのランキング


今は直接記事にPVを加算していますが、別テーブルから期間ごとのランキングや上昇度を測ってYoutubeの急上昇みたいな機能もほしいですね。



参考にしたところ


 


本番環境構築について


そもそもデプロイの経験が初めてだったのでdevelopment / production の棲み分けがわかってませんでした。
そのときに参考にした記事がこちら
(初心者向け)vpsを契約して、capistrano3でRailsアプリをデプロイするまで [その1 サーバー設定編]


https://qiita.com/ryo2132/items/f62690f0b16ec11270fe

Rails


Modelを簡素化するscope
Railsでよく利用する、Scopeの使い方。
https://qiita.com/ngron/items/14a39ce62c9d30bf3ac3


本番環境でbundlerがエラー起こしたときに参照しました。
BUNDLED WITH で Gemfile.lock が更新されてしまう件
https://qiita.com/suu_g/items/2b1630b8015d51c5292e


 


CSS


Flexboxについて
日本語対応!CSS Flexboxのチートシートを作ったので配布します
https://www.webcreatorbox.com/tech/css-flexbox-cheat-sheet


 


まとめ



初心者がサイトを開発したい場合は


・フレームワーク
・サーバーサイド言語
を軽く学んだ上で


・作りたいサイトのコンセプト
・作りたいサイトの機能
を紙に起こして


・サイトマップ
・データベースのER図


を元にして開発するのがいいです。
その際に「こうしたい」という機能がたくさんあると思いますが、それを大胆に1つか2つまで削ぎ落として、シンプルなものを完成させるのがいいです。できることよりできないことのほうが多いですから。


また、
・サーバーサイド言語
・フレームワークとデータベース
・デザイン
がWebアプリでは大事かな、と思います。


この記事も参考にするといいです。
(兎に角)早くプロトタイプを作る技術(初心者向け)
https://qiita.com/teradonburi/items/1d629a751ed42e923d7d


プログラミング初心者ゆえ、みなさんのアドバイス待ってます。


それではみなさん、よきシコい人妻http://shiko-tsuma.clubライフを。


PS
あとよくプログラミングスクールで謳われている「3ヶ月でフリーランスになる」ことは不可能だと思います。
開発してみて身にしみましたが、一口にプログラミングと言ってもやることが多すぎるので3ヶ月程度では仕事にするのはほぼ不可能だと思います。


現役エンジニアの人はどう思いますか?


また、メンターさんはプログラミング歴20年だし、知り合いのRubyプログラマは仕事ができるまで死ぬほど働いて3,4年かかったと言っていました。そんな人達がゴロゴロいるなかで勝てると思いますか?


知り合いの東大卒がこう言っていました。「俺は小学生から12年間、週に4回以上塾通い&一貫中高&自学自習(1日5時間以上)でギリギリ東大入れた。だから1年やそこらの努力で東大とか言っている有名人がムカつく。」と。
「じゃあ、100万払えば偏差値30でも3ヶ月で東大合格できる塾ってどう思う?」
「100%うそ、ありえない。受かる人は元々素地がある人。そもそも予備校の東大合格者の数だって、元々模試でA判定の人を無料で通わせたのをカウントしただけ。」



100万払えば3ヶ月で東大に合格させる塾なんてないのと同じように、50万払って3ヶ月でスーパープログラマーになれると思いますか?



みなさん、いいシコ妻ライフを。http://shiko-tsuma.club

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ActionCableにおいてのcurrent_userについて読み解く|Rails

action cableのcurrent_userについて読み解く

本記事投稿の経緯

Railsで簡単なチャットアプリを作成中、ActionCable内でのcurrent_userの取り扱いにつまづいたのでメモ。

環境

Rails 5.2.3(最新じゃないンゴオオオォ)
Ruby 2.4.0(最新じゃないンゴオオオォ)

connection.rbで定義されるcurrent_user

connection.rb
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = find_verified_user
    end

    protected
    def find_verified_user
    remember_token = User.encrypt(cookies[:user_remember_token][:value]) if cookies[:user_remember_token]
      if current_user = User.find_by(remember_token: remember_token)
        current_user
      else
        reject_unauthorized_connection
      end
    end
  end
end

上記は公式ドキュメントのサンプルコードであるが、ActionCableはWebSocketで通信する際、まずここで認証を行える。その際、current_userを定義しているのだが、こいつの仕組みがイマイチ把握できなかったのでソースコードを読み解いていくことにした。

identified_by

まずはidentified_byを見ていく。(コードは一部です。)

action_cable/connection/identification.rb
included do
  class_attribute :identifiers, default: Set.new
end

module ClassMethods
  # Mark a key as being a connection identifier index that can then be used to find the specific connection again later.
  # Common identifiers are current_user and current_account, but could be anything, really.
  #
  # Note that anything marked as an identifier will automatically create a delegate by the same name on any
  # channel instances created off the connection.
  def identified_by(*identifiers)
    Array(identifiers).each { |identifier| attr_accessor identifier }
    self.identifiers += identifiers
  end
end

コメントに大体が書いてあるのだが、identifier_byは後で特定のconnectionを見つけられるようにキーを作っている。今回であればcurrent_userをキーとしてconnectionを識別できるようにしてくれているのだ。
更にはそれを子であるchannelに自動で同じ名前でdelegateするという。

Connections form the foundation of the client-server relationship. For every WebSocket accepted by the server, a connection object is instantiated. This object becomes the parent of all the channel subscriptions that are created from there on.

そういえばrails guideにもconnectionはそこから作成されるchannnelの全ての親となると書いてあった。
(これややこしいが継承のことではないっぽい?。、delegateしてるし...それはまた今度、気が向けば記事にします。)

つまり、WebSocketの接続の識別としてcurrent_userは使われており、更には子であるchannelでも利用可能であるはず。

てわけで使ってみる。

実際にcurrent_userを使ってみる

流れ

⓪チャットルームモデルとユーザーモデルは多対多の関係。(ここは省略。)
①connection接続時にユーザーがログイン済みユーザーか認証し、current_userに接続したユーザーを入れる
②room_channelにサブスクライブする時、先のcurrent_userにそのチャンネルに接続する権限があるか認証する。

connection.rbは上記のサンプルコードと同じにします。
room_channel.rbのサブスクライブ部分は下記のとおり。

room_channel.rb
class RoomChannel < ApplicationCable::Channel

  def subscribed
    if !params.empty? && current_user.is_member?(params['room_id'])
      stream_from "room_channel_#{params['room_id']}"
    else
      reject
    end
  end

end

クライアントからチャットルームのidを送ってもらい、そのルームにログイン中のユーザーが属しているか確認している。
is_member?メソッドは下記のような感じで。

user.rb
  def is_member?(room_id)
    members = Room.find(room_id).users
    members.any?{|member| member.id == self.id }
  end

こんな感じでcurrent_userを使って各チャンネルでも認証をできる。

is_member?メソッドに指摘が入ったので修正(20190927)

下記はexists?を使ったver。
ここ、色々書き方あるが、どれが一番リソースに優しいか考えてたら、記事にできそうだったので今度書きます。(SQL力が足りない...)

user.rb
  def is_member?(room_id)
    Room.find(room_id).users.exists?(self.id)
  end

まとめ

connection時、ユーザーがログイン済みか認証し、ログインしていればそれをcurrent_userにいれ、子のチャンネルで利用可能にする。
current_userを使えばチャンネル内でも認証可能。

あとがき

実は今回、current_userの取り扱いでつまづいたのはrspecでテスト中のことでして、
current_userはチャンネル内で使えるって聞いたんだけど!!」
とか叫いたりして、
「本当に使えんのか?.....調べてやる!」
と思い執筆しました。

結局、testコードの書き方が間違っていただけでしたンゴ!!めでたしめでたし!!

まあためにはなったかな。。。

参考記事

【ActionCable】チャンネル接続/購読時にユーザ認証を行う
これはMUST!ActiveSupport の Class#class_attribute を使おう!
Rubyのdelegateについて整理する
Rails Guide

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS IoT EnterpriseでJobcanの打刻ができる出勤ボタンをつくった

背景

  • AWS LambdaがRubyの対応したので、自分で作成して使ってみたかった
  • Amazonがダッシュボタン事業から撤退してしまうので、その弔いとして
  • ジョブカンで打刻するのが億劫だなと感じていた

構成

構成としては以下の図のような構成になっています。

slack-jobcan.002.png

参考にしたのは、このブログのリンクでpythomで自分が実現したいことを達成していたので、90%くらいはここから真似してきました。

物理出勤ボタンを作った

こちらのブログだとpythonで書かれていたのですが、今回使いたかったのはRubyなので、Rubyで書きました。

手順

  • Amazon IoT Enterprise をAmazonで購入
    • 2500円します。
    • 実験としてはいいけど、高いな。。。

AWS IoT エンタープライズボタン – IoT をシンプルに。

  • Amazon IoT Enterpriseをセットアップ
    • アプリをダウンロード
    • バーコードを読み込んで、デバイスを登録する
    • デバイスとWifiを繋げる
  • Amazon IoTをセットアップ
    • プロジェクトを作成
    • デバイステンプレートを作成
  • Labda のセットアップ
    • 関数を作成
  • SlackのAPI token とチャンネルのIDの獲得
    • ここから獲得
  • コードの記述
    • slack-ruby-clientというgemを使う
    • 以下のようなコードを書きます
require 'slack-ruby-client'

def hello(event:, context:)
  Slack.configure do |config|
    config.token = ENV['SLACK_API_TOKEN']
  end
  client = Slack::Web::Client.new
  client.chat_command(channel: ENV['SLACK_CHANNEL'], command:'/jobcan_touch')
end
  • 詳しいコードは以下のGithubから確認してください。

https://github.com/knsg16/slack-jobcan

  • コードをAWS Lambdaにアップする
    • zip形式のフォルダを作成
    • 当該ファイルをLambdaにアップする
    • Lambdaにテストイベントを作成して、テスト
  • 完成
    • お疲れ様でした!!

トラブルシューティング

  • ステルスの社内WIFIにAmazon IoT Enterpriseを接続できない。。。
  • rubyのversionを2.5.0にきっちり揃えないといけなかった。。。
  • 普通にwebhookでは、コマンドをAPIで叩くことができなかった。かつ、非推奨のレガシートークンを使用しなければならなかった。。。
  • lamdaにアップするためにファイルをzipにする時のファイル構成
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby 2.7 で in 演算子が使えるようになる

ブログ記事からの転載になります。

Ruby 2.7 で in 演算子が使えるようになります。
そもそも in 演算子ってなんやねんって話なんですが、同じく Ruby 2.7 で入る予定のパターンマッチ構文の in になります。
例えばパターンマッチを使用することで次のような判定を行うことが出来ます。

def check user
  # name に m が含まれていて age が 14以下
  case user
  in { name: /m/, age: (..14) }
    "OK"
  else
    "NG"
  end
end

p check(id: 1, name: "Homu", age: 13)
# => OK
p check(id: 2, name: "mami", age: 15)
# => NG

このようにパターンマッチを使用する事でより細かく条件を指定する事が出来ます。

in 演算子を使う

上記の例だと case <expr>; in <pattern>; ... end みたいな構文を書く必要があるのでやや冗長に見えますね。
そこで in 演算子です。
in 演算子を使うことでパターンマッチ式を1行で書くことが出来ます。

def check user
  # name に m が含まれていて age が 14以下
  # in 演算子は true / false を返す
  (user in { name: /m/, age: (..14) }) ? "OK" : "NG"
end

p check(id: 1, name: "Homu", age: 13)
# => OK
p check(id: 2, name: "mami", age: 15)
# => NG

in 演算子はパターンマッチと同様に <expr> in <pattern> と書くことが出来ます。
また戻り値は true / false になります。
これは次のような select などと組み合わせるとより強力になります。

users = [
    { id: 1, name: "Homu", age: 13 },
    { id: 2, name: "mami", age: 14 },
    { id: 3, name: "Mado", age: 21 },
    { id: 4, name: "saya", age: 14 },
]

# @1 から _1 に変わったナンパラを使うとより簡潔にかける
p users.select { _1 in { name: /m/, age: (..14) } }
# => [{:id=>1, :name=>"Homu", :age=>13}, {:id=>2, :name=>"mami", :age=>14}]

これめっちゃ便利そうですね!!!

ちなみに in 演算子なのか in 式なのかどっちが正しいんですかね?

matz が in operaetor と書いていたので in 演算子と明記しましたが in 式のほうが正しいのかな。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VRChat APIで二段階認証する

VRChat APIを利用する際にAPI経由でVRChatにログインしてトークンを取得することが多いのですが、二段階認証を利用している場合については記事が無かったのでメモします

ざっくり概要を書くと、
まず通常ログインでトークンを取得。ただし二段階認証が有効になっている場合、トークンは使えない
トークン取得後に二段階認証する(Google Authenticatorから取得した番号をPOSTする)とトークンが有効化される
といった流れになります。

注意

この記事で使用しているVRChat APIの仕様は非公式なドキュメントに基づくものです。
公式からは悪意のないAPI利用はいいけど他人にPW入力させたらダメと言及されていますので、その点ご注意ください。

1. VRChatから認証トークンを取得

まずはVRChatからトークンを取得します。手順は以下の通りです。
1. https://api.vrchat.cloud/api/1/config をGETしてapiKeyを取得
2. https://api.vrchat.cloud/api/1/auth/user をGETしてauthTokenを取得 (VRChatのユーザ名・パスワードでBasic認証を行う)
※Basic認証時、トークンはCookieに返ってくるので注意!

rubyで書くとこんな感じです。GETするあたりは最低限必要な分だけ書いてます。

VRChatBasicAuth
def login_vrc
    username = "VRCのユーザー名"
    password = "VRCのパスワード"
    api_base = "https://api.vrchat.cloud/api/1"

    # 後々二段階認証で使うのでapiKeyとauthTokenはセッションに保管しています
    # apiKey取得
    apikey = get_apiKey(api_base)
    session[:apiKey] = apiKey

    # authToken取得
    session[:authToken] = get_authToken(api_base, apikey, username, password)
end

def get_apikey(api_base)
    endpoint = "/config"
    url = api_base + endpoint
    uri = URI.parse(url)
    req = Net::HTTP::Get.new(uri)
    Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https'){ |http| 
      response = http.request(req)
      @json = response.body
    }
    apikey_json = JSON.parse(@json)
    return apikey_json["clientApiKey"]
end

def get_authToken(api_base, apikey, username, password)
    endpoint = "/auth/user"
    url = api_base + endpoint
    uri = URI.parse(url)
    uri.query = URI.encode_www_form(apiKey: apikey)
    req = Net::HTTP::Get.new(uri)
    req.basic_auth(username, password)
    Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') { |http| 
      response = http.request(req)
      @cookie = response.get_fields('Set-Cookie')
    }

    #取得したCookieからauthTokenを抽出する(かなり強引なので正直直したい)
    cookie = @cookie.select { |x|
      x.include?("auth")
    }
    unless cookie[0].nil?
      return cookie[0]
    end
end

2. トークンを有効化

先ほど取得したapiKeyとauthToken、それとGoogle Authenticatorから取得した番号をhttps://api.vrchat.cloud/api/1/auth/twofactorauth/totp/verify にPOSTします。

認証が成功するとverified = tureが返ってきて、authTokenが各種APIで利用できるようになります。

VRChat2FA
def twofa_vrc
    num = "Google Authenticatorから取得した番号"
    api_base = "https://api.vrchat.cloud/api/1"
    endpoint = "/auth/twofactorauth/totp/verify"
    url = api_base + endpoint
    uri = URI.parse(url)
    uri.query = URI.encode_www_form(apiKey: session[:apikey])
    req = Net::HTTP::Post.new(uri)
    req["Cookie"] = session[:auth_token]
    req.set_form_data('code' => num)
    Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') { |http| 
      response = http.request(req)
      @json = response.body
    }

    #認証成功するとtwofa_result = trueになります
    twofa_result = JSON.parse(@json)["verified"]
end

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[初学者]よく起きるエラー 〜syntax error, unexpected end-of-input, expecting keyword_end〜

目的

学習の備忘録と初学者の参考資料として投稿

syntax error, unexpected end-of-input, expecting keyword_endというエラー

コードのエラーってよく赤字で英語表記だから、読む気にならねぇ〜っ:dizzy_face:
しかも焦っちゃう、、、、、:sweat_smile:
実際に単語を訳して見ると

単語訳

syntax・・・構文  
unexpected・・・予想外・予期しない 
expecting・・・期待して  
                       ...etc

つまり

endがねぇよ!!ってこと

ハイハイ、書きます書きます:writing_hand:

まとめ

エラーはパソコンが親切に教えてくれてることなので、恐れず・焦らずに対処したほうがいい。
まずはエラー文を読んでみて、何が書いてるのか理解すれば、簡単に解決することも多い。
                                         
今後も学習で気づきや参考になるものがあれば、アップしていきます。
もし参考になったらいいね!!よろしくお願いします:bow_tone1:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[初学者]シングルクォーテーションとダブルクォーテーションの違い(ruby)

目的

学習の備忘録と初学者の参考資料として投稿

シングルクォーテーションとダブルクォーテーション

ネットや参考書などでよく見られる’’や””の違いについての話。特にプログラミングを初めて勉強する方にとって、どちらにするかで非常に悩ましい場面に遭遇することがあります。

sample.rb
'Hello world' 
=>Hello world

"Hello world"
=>Hello world

こんな感じの:grin:

ダブルクォーテーションの役割

式展開や改行文字列を入れたい場合はダブルクォーテーションで囲む必要がある。

sample.rb
puts "Hello \n world" **改行文字列**
=>Hello
  world

name = Taro
puts "Hello #{name}" **式展開**
=>Hello Taro

まとめ

基本はダブルクォーテーションでいいのでは:point_up:
いろんな記事を見てるとダブルクォーテーションだと読み込み速度が遅くなるやシングルクォーテーションを書いて、視認性をよくするなどなど書いてあるが、特に気にしなくてもいい:ok_hand:

今後も学習で気づきや参考になるものがあれば、アップしていきます。
もし参考になったらいいね!!よろしくお願いします:bow_tone3:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails Webアプリケーション管理者権限でユーザー削除出来なかったエラー(初心者)

動作確認で管理者としてユーザーを削除しようとしたらエラー発生。

忘れないために自分用忘備録

以下エラー文
ActiveRecord::StatementInvalid in UsersController#destroy

Mysql2::Error: Cannot delete or update a parent row: a foreign key constraint fails・・・・・

原因

dependent: :destroy書き忘れていた為に発生したエラー
要するに、削除しようとしているユーザーの投稿、follower、お気に入り等どうするのって話。

対策

UserテーブルとPostテーブルを関連付けするために以下のコードを追加し、
Userのデータが削除されたときに、関連するPostのデータを削除するように設定。

dependent: :destroy

app/models/user.rb
class User < ApplicationRecord
  has_many :posts, dependent: :destroy
  has_many :follows, dependent: :destroy
  has_many :favorites, dependent: :destroy
end

これでユーザーに関連付けられたデータは削除完了。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

#Ruby 2.5 では 1行のBodyだけのCSV ファイルで headers が取得できないっぽい? ( Ruby 2.6 では可能 )

Unable to headers with only one line no body CSV file in Ruby 2.5

Ruby 2.5

require 'csv'

CSV.parse("A,B,C", headers: true).headers
# => []

CSV.parse("A,B,C\nX,Y,Z", headers: true).headers
# => ["A", "B", "C"]

CSV.parse("A,B,C", headers: true).to_s
# => "\n"

以下で動作確認。

  • ruby 2.5.2p104 (2018-10-18 revision 65133) [x86_64-darwin18]
  • ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-darwin18]
  • ruby 2.5.6p201 (2019-08-28 revision 67796) [x86_64-linux] (docker+mac)

Ruby 2.6

require 'csv'

CSV.parse("A,B,C", headers: true).headers
# => ["A", "B", "C"]

CSV.parse("A,B,C\nX,Y,Z", headers: true).headers
# => ["A", "B", "C"]

CSV.parse("A,B,C", headers: true).to_s
# => "A,B,C\n"

以下で動作確認。

  • ruby 2.6.0p0 (2018-12-25 revision 66547) [x86_64-darwin18]
  • ruby 2.6.1p33 (2019-01-30 revision 66950) [x86_64-darwin18]
  • ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin18]
  • ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux] (docker+Mac)

Docs

どこに違いがあるのだろう?

https://ruby-doc.org/stdlib-2.5.3/libdoc/csv/rdoc/CSV.html#method-i-headers
https://ruby-doc.org/stdlib-2.6.1/libdoc/csv/rdoc/CSV.html#method-i-headers

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/2534

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

これで大抵賄える!AWS SDK for Ruby V3でのS3操作まとめ!!

S3の操作するコードを書くタスクは日常的にやるわけではなくて、なぜかちょうど忘れた頃の絶妙なタイミングでやってきます。そのたびにAPIリファレンスを見ながら頑張って組み立ててるような気がしたので、しゃらくせぇ!と思い、よく使うやつをまとめました。

Aws::S3::ClientAws::S3::Resourceの2系統を用意しました。お好みに応じて参照してください。
参考までに、この2つの違いがよく分からなくて途方に暮れている人はこちらをご覧ください。
AWS SDK for Ruby V3のAws::S3::ClientとAws::S3::Resourceの違いに正面から向き合う

クレデンシャル情報やリージョンは、環境変数やIAMロール等で設定されているものとします。
個別に設定する場合は、Aws::S3::Client.newAws::S3::Resource.newの引数に渡してあげてください。

Aws::S3::Client

# 共通
client = Aws::S3::Client.new

バケット一覧

https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#list_buckets-instance_method

# バケット情報一覧
client.list_buckets
=> #<struct Aws::S3::Types::ListBucketsOutput
 buckets=
  [#<struct Aws::S3::Types::Bucket name="bucket-01", creation_date=2017-09-16 14:07:03 UTC>,
   #<struct Aws::S3::Types::Bucket name="bucket-02", creation_date=2018-08-19 04:22:41 UTC>,],
 owner=#<struct Aws::S3::Types::Owner display_name="XXXXXX", id="xxxxxxxxxxxxxxx">>

# 各バケットを操作
client.list_buckets.buckets.each do |bucket|
  # なにかする
end

バケット作成

https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#create_bucket-instance_method

client.create_bucket(bucket: "bucket-03")
=> #<struct Aws::S3::Types::CreateBucketOutput location="http://bucket-03.s3.amazonaws.com/">

バケット削除

https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#delete_bucket-instance_method

client.delete_bucket(bucket: "bucket-03")
=> #<struct Aws::EmptyStructure>

オブジェクト一覧

https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#list_objects_v2-instance_method

# オブジェクト情報一覧
# `list_objects`は使わない
client.list_objects_v2(bucket: "bucket-01")
=> #<struct Aws::S3::Types::ListObjectsV2Output
 is_truncated=true,
 contents=
  [#<struct Aws::S3::Types::Object key="example_01.txt", last_modified=2019-09-24 21:42:10 UTC, etag="\"xxxxxxxxxxxxxx\"", size=7, storage_class="STANDARD", owner=nil>,
   #<struct Aws::S3::Types::Object key="example_02.txt", last_modified=2019-09-24 21:42:10 UTC, etag="\"xxxxxxxxxxxxxx\"", size=7, storage_class="STANDARD", owner=nil>],
 name="bucket-01",
 prefix="",
 delimiter=nil,
 max_keys=1000,
 common_prefixes=[],
 encoding_type=nil,
 key_count=1000,
 continuation_token=nil,
 next_continuation_token="1I+AdtAyjlXhJ1jEKpdpR3fqz2bDnfJFEfZ+wgn9vmpXjHNGcLzL8bg==",
 start_after=nil>

# 各オブジェクトを操作(1000個以下の場合)
client.list_objects_v2(bucket: "bucket-01").contents.each do |object|
  # なにかする
end

# 各オブジェクトを操作(1000個より多いの場合)
options = {bucket: "bucket-01"}
loop do
  object_list = client.list_objects_v2(options)
  object_list.contents.each do |object|
    # なにかする
  end
  options[:continuation_token] = object_list.next_continuation_token
  break unless object_list.next_continuation_token
end

# キー名のプレフィクスによる絞り込み
client.list_objects_v2(bucket: "bucket-01", prefix: "hoge/").contents.each do |object|
  # なにかする
end

オブジェクトアップロード

https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#put_object-instance_method

client.put_object(bucket: "bucket-01", key: "example_01.txt", body: "example")
=> #<struct Aws::S3::Types::PutObjectOutput
 expiration=nil,
 etag="\"xxxxxxxxxxxxxx\"",
 server_side_encryption=nil,
 version_id="JlDwEydDxlVNoyEaChzromzkjPo5FwVl",
 sse_customer_algorithm=nil,
 sse_customer_key_md5=nil,
 ssekms_key_id=nil,
 ssekms_encryption_context=nil,
 request_charged=nil>

オブジェクト読み込み

https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#get_object-instance_method
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Types/GetObjectOutput.html

client.get_object(bucket: "bucket-01", :key => 'example_01.txt').body.read
=> "example"

オブジェクト削除

https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#delete_object-instance_method

client.delete_object(bucket: "bucket-01", key: "example_01.txt")
=> #<struct Aws::S3::Types::DeleteObjectOutput delete_marker=true, version_id="skCZWAXC0.vhxjK6zKTF0dBkj4H0gEe8", request_charged=nil>

オブジェクトのバージョン操作

https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#list_object_versions-instance_method
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#delete_object-instance_method

# バージョン情報
client.list_object_versions(bucket: "bucket-01", prefix: "example_01.txt")
=> #<struct Aws::S3::Types::ListObjectVersionsOutput
 is_truncated=false,
 key_marker="",
 version_id_marker="",
 next_key_marker=nil,
 next_version_id_marker=nil,
 versions=
  [#<struct Aws::S3::Types::ObjectVersion
  etag="\"xxxxxxxxxx\"",
  size=7,
  storage_class="STANDARD",
  key="example_01.txt",
  version_id="BcczH4ZRVunnKyBuzk3cBOQgrh1gtGpR",
  is_latest=true,
  last_modified=2019-09-24 21:47:54 UTC,
  owner=#<struct Aws::S3::Types::Owner display_name="xxxxxx", id="xxxxxxxxxxxxxxxxxx">>,
 #<struct Aws::S3::Types::ObjectVersion
  etag="\"xxxxxxxxxx\"",
  size=7,
  storage_class="STANDARD",
  key="example_01.txt",
  version_id="null",
  is_latest=false,
  last_modified=2019-09-24 21:42:10 UTC,
  owner=#<struct Aws::S3::Types::Owner display_name="xxxxxx", id="xxxxxxxxxxxxxxxxxx">>],
 name="bucket-01",
 prefix="example_01.txt",
 delimiter=nil,
 max_keys=1000,
 common_prefixes=[],
 encoding_type="url">

# 特定のバージョンを削除
client.delete_object(bucket: "bucket-01", prefix: "example_01.txt", version_id: "JlDwEydDxlVNoyEaChzromzkjPo5FwVl")
=> #<struct Aws::S3::Types::DeleteObjectOutput delete_marker=nil, version_id="JlDwEydDxlVNoyEaChzromzkjPo5FwVl", request_charged=nil>

マルチパートアップロード

https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#create_multipart_upload-instance_method
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#upload_part-instance_method
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#complete_multipart_upload-instance_method

multipart = client.create_multipart_upload(bucket: "bucket-01", key: "multipart")
=> #<struct Aws::S3::Types::CreateMultipartUploadOutput
 abort_date=nil,
 abort_rule_id=nil,
 bucket="bucket-01",
 key="multipart",
 upload_id="xxxxxxxxxxxxxxxxxxxx",
 server_side_encryption=nil,
 sse_customer_algorithm=nil,
 sse_customer_key_md5=nil,
 ssekms_key_id=nil,
 ssekms_encryption_context=nil,
 request_charged=nil>

part_01 = client.upload_part(bucket: "bucket-01", key: "multipart", body: File.open("/path/to/part_01"), part_number: 1, upload_id: multipart.upload_id)
=> #<struct Aws::S3::Types::UploadPartOutput
 server_side_encryption=nil,
 etag="\"xxxxxxxxxxxxxxxxxxxxx\"",
 sse_customer_algorithm=nil,
 sse_customer_key_md5=nil,
 ssekms_key_id=nil,
 request_charged=nil>

part_02 = client.upload_part(bucket: "bucket-01", key: "multipart", body: File.open("/path/to/part_02"), part_number: 2, upload_id: multipart.upload_id)
=> #<struct Aws::S3::Types::UploadPartOutput
 server_side_encryption=nil,
 etag="\"yyyyyyyyyyyyyyyyyyyyy\"",
 sse_customer_algorithm=nil,
 sse_customer_key_md5=nil,
 ssekms_key_id=nil,
 request_charged=nil>

client.complete_multipart_upload(
  bucket: "bucket-name-0001",
  key: "multipart",
  multipart_upload: {
    parts: [
      {etag: part_01.etag, part_number: 1},
      {etag: part_02.etag, part_number: 2}
    ]
  },
  upload_id: multipart.upload_id
)
=> #<struct Aws::S3::Types::CompleteMultipartUploadOutput
 location="https://bucket-01.s3.ap-northeast-1.amazonaws.com/multipart",
 bucket="bucket-01",
 key="multipart",
 expiration=nil,
 etag="\"zzzzzzzzzzzzzzzzzzzzz\"",
 server_side_encryption=nil,
 version_id="CSbIGreIivX2CTbIxj9Qz1JbePVQb2Qo",
 ssekms_key_id=nil,
 request_charged=nil>

署名付きURL作成

https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Presigner.html

# clientではできない
signer = Aws::S3::Presigner.new
signer.presigned_url(:get_object, bucket: "bucket-01", key: "example_01.txt", expires_in: 3600)
=> "https://bucket-01.s3.ap-northeast-1.amazonaws.com/example_0001?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-...

Aws::S3::Resource

# 共通
resource = Aws::S3::Resource.new

バケット一覧

https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Resource.html#buckets-instance_method

# バケット情報一覧
resource.buckets
=> #<Aws::S3::Bucket::Collection:0x00007fb1e3ccb6a8 @batches=#<Enumerator: ...>, @limit=nil, @size=nil>

# 各バケットを操作
resource.buckets.each do |bucket|
  # なにかする
end

バケット作成

https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Resource.html#create_bucket-instance_method

resource.create_bucket(bucket: "bucket-03")
=> #<Aws::S3::Bucket:0x00007fb1e3d3f2d8 @client=#<Aws::S3::Client>, @data=nil, @name="bucket-03">

# こちらでもできるけど、Client系統で扱うbucket情報が返ってくる
resource.bucket("bucket-03").create

# 存在確認もできる
resource.bucket("bucket-03").exists?
=> true

バケット削除

https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Bucket.html#delete-instance_method

resource.bucket("bucket-03").delete
=> #<struct Aws::EmptyStructure>

オブジェクト一覧

https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Bucket.html#objects-instance_method

bucket = resource.bucket("bucket-01")

# オブジェクト情報
bucket.objects
=> #<Aws::S3::ObjectSummary::Collection:0x00007fb1e3e47a68 @batches=#<Enumerator: ...>, @limit=nil, @size=nil>

# 各オブジェクトを操作(1000個より多くてもOK!)
bucket.objects.each do |object|
  # なにかする
end

# キー名のプレフィクスによる絞り込み
bucket.objects(prefix: "hoge/").each do |object|
  # なにかする
end

オブジェクトアップロード

https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Bucket.html#put_object-instance_method
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#put-instance_method

bucket = resource.bucket("bucket-01")

# 文字列から
bucket.put_object(key: "example_01.txt", body: "example")
=> #<Aws::S3::Object:0x00007f859e9625c8 @bucket_name="bucket-01", @client=#<Aws::S3::Client>, @data=nil, @key="example_01.txt">

# ファイルから
bucket.put_object(key: "example_01.txt", body: File.open("example_01.txt"))
=> #<Aws::S3::Object:0x00007f859e9625c8 @bucket_name="bucket-01", @client=#<Aws::S3::Client>, @data=nil, @key="example_01.txt">

# こちらでもできるけど、Client系統で扱うbucket情報が返ってくる
bucket.object("example_01.txt").put(body: "example")
=> #<struct Aws::S3::Types::PutObjectOutput
 expiration=nil,
 etag="\"xxxxxxxxxxxxxxxxxxxxxxxxxxxx\"",
 server_side_encryption=nil,
 version_id=nil,
 sse_customer_algorithm=nil,
 sse_customer_key_md5=nil,
 ssekms_key_id=nil,
 ssekms_encryption_context=nil,
 request_charged=nil>

オブジェクト読み込み

https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#get-instance_method

bucket = resource.bucket("bucket-01")

bucket.object("example_01.txt").get.body.read
=> "example"

# 存在確認もできる
bucket.object("example_01.txt").exists?
=> true

オブジェクト削除

https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Bucket.html#delete-instance_method
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/ObjectSummary/Collection.html#batch_delete!-instance_method

bucket = resource.bucket("bucket-01")

bucket.object("example_01.txt").delete
=> #<struct Aws::S3::Types::DeleteObjectOutput delete_marker=nil, version_id=nil, request_charged=nil>

# 一括削除
bucket.objects(prefix: "hoge/").batch_delete!
=> nil

オブジェクトのバージョン操作

https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Bucket.html#object_versions-instance_method
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#delete-instance_method

bucket = resource.bucket("bucket-01")

bucket.object_versions(prefix: "example_01.txt").map(&:data)
=> [#<struct Aws::S3::Types::ObjectVersion
  etag="\"xxxxxxxxxx\"",
  size=7,
  storage_class="STANDARD",
  key="example_01.txt",
  version_id="BcczH4ZRVunnKyBuzk3cBOQgrh1gtGpR",
  is_latest=true,
  last_modified=2019-09-24 21:47:54 UTC,
  owner=#<struct Aws::S3::Types::Owner display_name="xxxxxx", id="xxxxxxxxxxxxxxxxxx">>,
 #<struct Aws::S3::Types::ObjectVersion
  etag="\"xxxxxxxxxx\"",
  size=7,
  storage_class="STANDARD",
  key="example_01.txt",
  version_id="null",
  is_latest=false,
  last_modified=2019-09-24 21:42:10 UTC,
  owner=#<struct Aws::S3::Types::Owner display_name="xxxxxx", id="xxxxxxxxxxxxxxxxxx">>]

# 特定のバージョンを削除
bucket.object("example_01.txt").delete(version_id: "BcczH4ZRVunnKyBuzk3cBOQgrh1gtGpR")
=> #<struct Aws::S3::Types::DeleteObjectOutput delete_marker=nil, version_id="BcczH4ZRVunnKyBuzk3cBOQgrh1gtGpR", request_charged=nil>

マルチパートアップロード

https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#upload_file-instance_method

bucket = resource.bucket("bucket-01")

# デフォルトだと15MBより大きければマルチパートアップロードになる
# しきい値を変えたい場合は、`multipart_threshold`オプションを与える
bucket.object("very_large_file.txt").upload_file("/path/to/very_large_file")
=> true

署名付きURL作成

https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#presigned_url-instance_method

bucket = resource.bucket("bucket-01")

# `:get`/`put`/`head`/`delete`が指定できる
# `expires_in`はデフォルト900秒
bucket.object("example_01.txt").presigned_url(:get, expires_in: 3600)
=> "https://bucket-01.s3.ap-northeast-1.amazonaws.com/example_0001?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsでシンプルなグラフを扱うならchart-js-rails よりchartkickを使うべし

Railsでグラフを扱う方法として chart-js-rails を使った方法を こちらの記事で解説しました。
一方、シンプルなグラフを埋め込む場合においては chartkick を使ったほうが簡単なことがわかったので本記事で解説します。

chartkickとは

chart.jsをいい感じにラッピングして、ワンラインでグラフを埋め込めるようにするプロジェクトです。
Rubyに限らず様々な言語で実装されています。

https://chartkick.com/
Chartkick_-_Create_beautiful_JavaScript_charts_with_one_line_of_Ruby.png

また、chart.jsだけでなくGoogle ChartsやHighchartsもサポートしています。

Chartkick_-_Create_beautiful_JavaScript_charts_with_one_line_of_Ruby.png

chartkickを使ったほうが良い理由

RubyとJavascript間のデータの受け渡しを意識しなくて良い

こちらの記事の方法だと、Ruby側で扱う数値を何らかの方法でJavascriptで受け渡す必要がありました。

chartkickの場合はデータを配列にしてRubyのメソッドへ渡すだけなので、Ruby側のみで実装が完結します。

githubのスターの数が多い

あくまでgemで比較した場合ですが、chartkickの方が断然スターが多いです。
直近のコミット日時もchartkickの方が新しいです。

Rails6にも対応している

Ruby on Rails6からデフォルトのJavascriptコンパイラがWebpackerとなります。
おそらく、chart-js-rails をRails6で使おうとすると若干の手間がかかると推測されます。

実際に使ってみる

サンプルアプリケーションを作る

今回は時事ネタとして「ラグビーワールドカップ開催国」に関する情報をグラフ化します。
以下コマンドを実行してアプリケーションを作成します。

$ rails new chartkick_sample
$ rails g scaffold rugby_world_cup_host_country name:string total_attendance:integer matches:integer stadium_capacity:integer held_at:datetime

db/seeds.rbWikipediaを参考にラグビーワールドカップの開催国を入力します。
※厳密には1991年と1987年にも開催されているのですが、開催国が複数あってDB構造が複雑化するので今回は1995年以降のみ入力しました。

db/seeds.rb
[
  [1995, 'South Africa', 1100000, 32, 1423850],
  [1999, 'Wales', 1750000, 41, 2104500],
  [2003, 'Australia', 1837547, 48, 2208529],
  [2007, 'France', 2263223, 48, 2470660],
  [2011, 'New Zealand', 1477294, 48, 1732000],
  [2015, 'England', 2477805, 48, 2600741],
  [2019, 'Japan', nil, 48, nil],
  [2023, 'France', nil, 48, nil],
].each do |record|
  RugbyWorldCupHostCountry.create(
    held_at: Time.zone.local(record[0]),
    name: record[1],
    total_attendance: record[2],
    matches: record[3],
    stadium_capacity: record[4]
  )
end

入力が終わったらDBの作成・マイグレーション・seedを実行します。

rails db:create db:migrate db:seed

http://localhost:3000/rugby_world_cup_host_countries へアクセスすると、一覧が表示されました。

ChartkickSample.png

グラフを表示する

chartkickのインストール

Gemfileに追記して、bundle install します。
続けて yarn add も実行してしまいましょう。

Gemfile
・・・省略・・・
gem 'bootsnap', '>= 1.4.2', require: false
gem "chartkick" # 追記

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end
・・・省略・・・
$ bundle install

ここからはRails6以降かRails5以前かで手順が分かれます。

Rails6以降

yarnchart.jsをインストールします。

$ yarn add chartkick chart.js

application.jschart.jschartkick をrequireします。

app/javascript/packs/application.js
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")

require("chartkick") // 追記
require("chart.js" // 追記
Rails5以前

application.js へ以下を追記します。

app/assets/javascripts/application.js
//= require chartkick
//= require Chart.bundle

グラフの実装

開催国一覧ページにグラフを埋め込みます。

app/views/rugby_world_cup_host_countries/index.html.erb
<p id="notice"><%= notice %></p>

<h1>Rugby World Cup Host Countries</h1>

<%= column_chart RugbyWorldCupHostCountry.pluck(:held_at, :total_attendance) %><!-- 追記 -->

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Total attendance</th>
      <th>Matches</th>
      <th>Stadium capacity</th>
      <th>Held at</th>
      <th colspan="3"></th>
    </tr>
  </thead>
・・・省略・・・

http://localhost:3000/rugby_world_cup_host_countries へアクセスするとグラフが表示されます。

ChartkickSample.png

オプションを指定すれば色やサイズも変えられます。

app/views/rugby_world_cup_host_countries/index.html.erb
<p id="notice"><%= notice %></p>

<h1>Rugby World Cup Host Countries</h1><!-- 追記 -->

<%= column_chart RugbyWorldCupHostCountry.pluck(:held_at, :total_attendance),
                 id: 'total-attendance-chart',
                 width: '400px',
                 height: '500px',
                 colors: ['#b00'] %><!-- 追記 -->

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Total attendance</th>
      <th>Matches</th>
      <th>Stadium capacity</th>
      <th>Held at</th>
      <th colspan="3"></th>
    </tr>
  </thead>
・・・省略・・・

ChartkickSample.png

chartkickでできそうにないこと

現時点で実現方法がわからなかったことを記載しておきます。
実現方法をご存知な方はぜひコメントをお願いします。

  • 2軸のグラフを表示する
  • 2種類のグラフ(折れ線グラフと棒グラフなど)を1つのグラフとして表示する

まとめ

RubyとJavascript間の値の受け渡しを意識しなくて良いですし、なにより1行でグラフを埋め込めるのが素晴らしいです。
Railsでシンプルなグラフを出力したい方はぜひ使ってみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby gem を調べる

Ruby gem を調べる

業務でRubyの簡単な改修をするようになり、当然のように既存のgemを利用していますが、改めてgemについて簡単にまとめてみました。
9bc6cc57395af0baa8f335ba96b2be69_s.jpg

gemとは何か

RubyにおけるgemとはRubyのライブラリのことです。
gemを使用することで1からコードを書かなくても機能を実装できるため便利です。
しかし、注意点としてフレームワークのバージョンによる相性やgemのバージョン同士の組み合わせによっては動作しない可能性があります。その為インストールやアンインストールにはRubyGemsやbundlerといったパッケージ管理ツールを使うことが多いです。
Rubyのバージョン1.9以降はRubyGemsは標準添付となっていますが、それ以前のバージョンの場合は自分でインストールする必要があります。

gemを探す

gemは主にRubyGems.orgに置いてあります。その他にはgemコマンドを使用したりWebサイトを直接閲覧したりして探します。
またruby関連のリポジトリとしてGitHubが広く使われている為、gemのソースコードのほとんどがGitHubで見ることができます。

gemコマンド

ターミナルでgemコマンドを使うことでgemをインストールしたり一覧を見ることができます。

$gem search -r #入手可能な全てのgemの一覧を調べます
--local(-r)オプションを使うと、インストール済みのgemに対してローカルで検索をすることが出来ます
$gem install [gem名] #gemをインストールします
--version/-vフラグを使うと、バージョンを指定してインストールすることができます。
$gem update #gemをアップデートします
$gem install bundler #未インストールのバンドルがある場合インストールします。
$gem list #インストール済みのgemを見ることができます。
$gem help #ドキュメントをターミナルで参照することができます。

bundleコマンド

gemのバージョン管理を行うコマンドです。
eab9eb13cb26e6a2a20ed224c7d94185_s.jpg

$gem install bundler #bundlerをインストールします。
$bundle install #これだけで使用するとRuby共通フォルダにgemをインストールします。
$bundle install --path #プロジェクトフォルダにgemをインストールします。
以降--pathオプションを付けなくてもプロジェクトフォルダにインストールされます。
$bundle install --without #デプロイする際に余計なgemを入れない為に使用します。
$bundle list #プロジェクトで使用しているgemのバージョンを確認します。

よく使われるgem

devise

IDとメールアドレスを使用して登録、ログインをする一般的なログイン機能を実装することが可能になります。
登録処理、仮登録、ログイン処理、認証機能、パスワードの変更/再発行などログイン機能として十分な機能を備えている為広く使用されています。

active admin

管理画面を自動で作成します。
権限付与・管理を行えるcancancanというgemなどをセットに使用することがよくあります。
また、管理画面を作成するのに使用されるgemは他にもrails adminやAdministrateなど有名なものがありますが、それぞれ特性がある為調べて使用しましょう。

i18n

Ruby on Rails 2.2以降からRailsに同梱されている多言語化に用いるgemです。
多くの言語ファイルが用意されていて、様々な言語を翻訳してくれる為サイトの国際化などに便利です。

Kaminari

長い文章の記事をページ分割して各ページへのリンクを表示してくれるページネーション機能を実装するGemです。
表示件数やデザインも簡単に設定変更できます。

better errors

エラーメッセージをわかりやすく表示してくれるgemです。
ブラウザからのirbによるdebugが可能で、どうなっているかわからない箇所ターミナルの表示をせずにすぐ確認できます。
デバッグ作業が捗る人気なgemです。

最後に

f622389b613ac3eeb40a6726b3491a90_s.jpg
今回はRubyのgemについて簡単な説明とよく使われるgemを少し紹介しました。
脱Ruby初心者に向けて既存のgemをただ使用するだけでなくソースを確認し一歩踏み込んだ読み方をして自分でもgemを作れるようになるとよりRubyを知ることができると思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む