- 投稿日:2020-04-01T23:44:30+09:00
Linux(ubuntu)にruby on rails 6.0の環境構築をする(2020.4.1)
どうものぶおです
今回はubuntuでruby on rails6の開発環境を作っていきますこれまでmacで開発していたんですが、別に持っていたwindowsのPCをHDDからSSDに変換して超サクサクに動くようになったため、こちらでruby on rails を使えるようにしていきたいと思います
Rubyをインストール
インストールに必要なaptを更新します
sudo apt-get updatesudo apt-get -y install git curl g++ make zlib1g-dev libssl-dev libreadline-dev libyaml-dev libxml2-dev libxslt-dev sqlite3 libsqlite3-dev nodejsホームディレクトリに移動します
cdgit clone git://github.com/sstephenson/rbenv.git .rbenvパスを通します
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrcecho 'eval "$(rbenv init -)"' >> ~/.bashrcexec $SHELLmkdir -p ~/.rbenv/plugins作成したディレクトリに移動
cd ~/.rbenv/pluginsgit上のファイルをクローンします
git clone git://github.com/sstephenson/ruby-build.gitrubyのバージョン(今回は2.6.3)を指定してinstallします
rbenv install 2.6.3使用するrubyのバージョンをコンピュータに知らせます
rbenv global 2.6.3rubyが正常にインストールされているか確認します(バージョンが表示されます)
ruby -vRuby on railsをインストール
(最新版の6.0がインストールされます)
gem install rails実際にRailsで作成できるか確認
まずはアプリを作成します
rails new sampe-app作成したアプリのディレクトリに移動します
cd sample-appサーバーを起動
rails sしかし以下のようなエラーが・・・。
︙ Webpacker configuration file not found /home/nobu/.rbenv/plugins/sample-app/config/webpacker.yml. Please run rails webpacker:install Error: No such file or directory書いてあるとおり
rails webpacker:install
を実行してみると今度は別のエラーがYarn executable was not detected in the system.Yarnがないのでインストールすることに
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.listsudo apt-get update && sudo apt-get install yarn完了しました。
もう一度webpacker
をインストールrails webpacker:install完了したらもう一度'rails s'でサーバーを起動し、ブラウザ(chromeやexplolerなど)でいかにアクセスします
(わからない人はコピーして検索欄に貼り付けましょう)localhost:3000これで以下のような画面がでたら完了です
(わーい)
- 投稿日:2020-04-01T23:05:55+09:00
Swiper.jsが動作しなかった時の対処法[備忘録]
はじめに
バージョン確認の重要性
Swiper.jsはバージョンによってサポートしてくれるブラウザが違う。ver5.0以降から、IEは完全にサポート対象外になった。今回私はchromeで開発を行っているので最新版でいけると思いきや、挙動が意図したものにならなかったので、4.5.0系を使うことにした。
便利なオプションたち
オプションが豊富で有名なSwiper.js
ここでは、私が参考にした記事を載せておくだけに留めておく。
参考記事
実例12パターン】画像スライダーはSwiper使っておけば間違いない!実用的な使い方を紹介
https://haniwaman.com/swiper/
swiper.js使ってみたからそのオプションについて(v4.1.6)
https://reiwinn-web.net/2018/03/15/swiper-4-1-6/実際のコード
swiper.js$(function(){ var mySwiper = new Swiper('.swiper-container', { slidesPerView: 3, slideToClickedSlide: true, pagination: { el: '.swiper-pagination', type: 'bullets', clickable: true }, navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev' }, breakpoints: { 767: { slidesPerView: 1, spaceBetween: 0 } } }); });html.haml.swiper-container.p-2.rounded .swiper-wrapper.p-1 - @posts.each do |post| .events__content.col-sm-6.col-md-4.mb-3 .swiper-slide .card{id: post.id} %label.m-1 - if post.image.present? %img.card-img-top.img-fluid.rounded{src: "#{post.image}"} - else %img.card-img-top.img-fluid.rounded{src: "/assets/noimage.png"} .card-body.event %h5= link_to "#{post.title}", post_path(post.id), class: "event-title stretched-link text-decoration-none" .event__name #{post.user.name} さん .text-right = l post.created_at, format: :long .swiper-button-prev .swiper-button-next .swiper-paginationこれでprevボタンnextボタンを押しても、スライドされない。consoleをみてもエラーはない。ドラッグしながらスクロールすると、一応.swiper-containerに要素が入っていることはわかる。jsファイルは読み込まれているが、メソッドが実行されていない。
階層構造は崩すな
原因がわかった。先ほどのコードは、.swiper-wrapperと.swiper-slidesの間に、.events__content......というクラスが入っている。これが原因だった。Swiper.jsのswiper-container, wrapper, slidesのクラスたちは必ず親子孫の関係でなくてはならなかった。
修正後のコード
html.haml.swiper-container.p-2.rounded .swiper-wrapper.p-1 - @posts.each do |post| .swiper-slide.events__content.col-sm-6.col-md-4.mb-3 .card{id: post.id} %label.m-1 - if post.image.present? %img.card-img-top.img-fluid.rounded{src: "#{post.image}"} - else %img.card-img-top.img-fluid.rounded{src: "/assets/noimage.png"} .card-body.event %h5= link_to "#{post.title}", post_path(post.id), class: "event-title stretched-link text-decoration-none" .event__name #{post.user.name} さん .text-right = l post.created_at, format: :long .card.col-auto .text-left - post.genre_list.each do |genre| .badge.badge-primary{data:{role: "tagsinput"}} = link_to "#{genre}", tag_path(genre), class: 'text-decoration-none text-white' - if user_signed_in? .offset-8.col-auto.card .text-center.likes = render partial: '/posts/posts', locals: {post: post} - if @posts.count >= 4 .swiper-button-prev .swiper-button-next .swiper-paginationこれで正常に動くようになった。
終わりに
私のように、こんなことで何時間も無駄にしないように、皆さんも気をつけてください。
参考記事
【Rails5】「Swiper」を使ってスライダー、カルーセルを作る方法
https://qiita.com/emincoring/items/18d07d0aec5d9836227c
[Rails]Swiperで画像スライド作成
https://qiita.com/yummy888/items/8528c7542f85ae7bbc55サンプル付き!簡単にスライドを作れるライブラリSwiper.js超解説(各種ナビゲーションカスタマイズ編)
https://garigaricode.com/swiper_navigation/
- 投稿日:2020-04-01T22:22:25+09:00
【Ruby】カードゲームZENOをターミナル上で、コンピューターと対戦
カードゲームZENOがターミナル上で、コンピューターと対戦できるアプリを作りました。
制作期間は5日間です。
ZENOをご存じない方の為に説明しますと、オリエンタルラジオの中田敦彦さんがプロデュースした新しいカードゲームです。UNOの売り上げをこえたことや、YOUTUBE上でメンタリストのDAIGOさんと白熱した心理戦を繰り広げられたことで話題になりました。
もともとはラブレターというカードゲームを題材として作られており、そこに中田敦彦さんが新たに手を加えて二ヶ月ほどで世界観や設定を作り上げました。AMAZONで770円ほどで購入することができます。
あまり興味がない方もいるかと思いますが、中田敦彦さんはDAIGOさん以外にも、ホリエモン、YOUTUBEのヒカルさん、クイズ王の伊沢拓司とも白熱した戦いをYOUTUBE上で公開してますのでそちらだけでもぜひ見てみてください。
自分が作ったアプリの動作ですが、最終ターンまでにいけたので恐らく不備はないかと思います。
ないと信じたい。
現在就活中ですので、今後WEB上でプレイできるようにしたり、コードもリファクタリングする予定です。
引数の数がえげつくなってたり、コードが冗長すぎるなどありますが、今後改善します。
とりあえず、友人への公開用にコードを公開してあります。
とりあえず動けばいいと思って作ったので、コードはあまり綺麗ではありません。zeno.rbrequire './decknone' require './mytern' require './pctern' require './tutorial' line = "------------------------------------------" zeno = [10,9,8,8,7,7,6,6,5,5,4,4,3,3,2,2,1,1] zeno = zeno.shuffle #自分の手札カード myhand = [] myhand[0] = zeno[0] zeno.delete_at(0) #相手の手札カード pchand = [] pchand[0] = zeno[0] zeno.delete_at(0) #山札の一番下にあるヒーローカード hero = [] hero[0] = zeno[0] zeno.delete_at(0) #自分の使用済みカード mydiscard = [] #相手の使用済みカード pcdiscard = [] mywiseman = [] mywiseman[0] = 0 pcwiseman = [] pcwiseman[0] = 0 myguard = [] myguard[0] = 0 pcguard = [] pcguard[0] = 0 #使用したカードの番号により分岐 puts "あなたが先行です" while true #デッキにカードがなかった場合、互いの数字の大きさで勝敗を決める。 if zeno == [] decknone(myhand, pchand) end #前回選んだカードが7番だった場合、賢者の効果を発動する。 if mywiseman[0] == 1 mywiseman[0] = 0 wiseman = zeno.first(3) puts "賢者のカードの効果により、カードをドローします" wiseman.each do |wise| puts "#{wise}番" end puts "あなたの現在の手札にあるのは#{myhand[0]}番のカードです" puts "どのカードを手札に加えますか" input = gets.to_i while !wiseman.any?(input) do puts "そのカード番号は引いたカードの中にありません" wiseman.each do |wise| puts "#{wise}番" end puts "再度番号を入力してください" input = gets.to_i end myhand[1] = input zeno.delete_at(zeno.find_index(input)) zeno.shuffle puts "#{myhand[1]}番のカードを手札に加えました" puts "残りのカードを山札に戻してシャッフルします" end #自分がカードを一枚ドローする。先行か後攻かを決めるメソッドをあとで作る if myhand.length == 1 myhand[1] = zeno[0] zeno.delete_at(0) puts "あなたはカードをドローしました" puts "引いたのは#{myhand[1]}番のカードです" end #カード選択画面を表示 puts line puts "あなたの手札にあるのは#{myhand[0]}番と#{myhand[1]}番のカードです" puts "どちらのカードを使用しますか" puts "#{myhand[0]}番のカードを使用する" puts "#{myhand[1]}番のカードを使用する" puts "0番:チュートリアルを閲覧する" puts "11番:使用済みカードを確認する" puts line input = gets.to_i while (input != myhand[0] && input != myhand[1]) || input == 10 || input == 11 do if input == 0 tutorial(line) puts line puts "あなたの手札にあるのは#{myhand[0]}番と#{myhand[1]}番のカードです" puts "どちらのカードを使用しますか" puts "#{myhand[0]}番のカードを使用する" puts "#{myhand[1]}番のカードを使用する" puts "0番チュートリアルを閲覧する" puts line input = gets.to_i elsif input == 10 puts line puts "英雄のカードは手札から使用することができません" puts "あなたの手札にあるのは#{myhand[0]}番と#{myhand[1]}番のカードです" puts "どちらのカードを使用しますか" puts "#{myhand[0]}番のカードを使用する" puts "#{myhand[1]}番のカードを使用する" puts "0番チュートリアルを閲覧する" puts line input = gets.to_i elsif input == 11 puts line puts "自分の使用済みカード" if mydiscard == [] puts "ありません" else mydiscard.each do |mydis| puts "#{mydis}番" end end puts "相手の使用済みカード" if pcdiscard == [] puts "ありません" else pcdiscard.each do |pcdis| puts "#{pcdis}番" end end puts line puts "その番号のカードは手札にありません" puts "あなたの手札にあるのは#{myhand[0]}番と#{myhand[1]}番のカードです" puts "どちらのカードを使用しますか" puts "#{myhand[0]}番のカードを使用する" puts "#{myhand[1]}番のカードを使用する" puts "0番:チュートリアルを閲覧する" puts line input = gets.to_i else puts line puts "あなたの手札にあるのは#{myhand[0]}番と#{myhand[1]}番のカードです" puts "どちらのカードを使用しますか" puts "#{myhand[0]}番のカードを使用する" puts "#{myhand[1]}番のカードを使用する" puts "0番:チュートリアルを閲覧する" puts "その番号のカードは手札にありません" puts line input = gets.to_i end end #選んだカードの番号によって分岐が別れる myhand.delete_at(myhand.find_index(input)) mydiscard << input mytern(myhand, pchand, input, zeno, hero, mydiscard, pcdiscard, mywiseman, pcwiseman, myguard, pcguard, line) pcguard[0] = 0 puts line pctern(myhand, pchand, zeno, hero, mydiscard, pcdiscard, mywiseman, pcwiseman, myguard, pcguard, line) myguard[0] = 0 puts line end自分のターンのメソッドはこちらに分けました。
mytern.rbdef mytern(myhand, pchand, card, zeno, hero, mydiscard, pcdiscard, mywiseman, pcwiseman, myguard, pcguard, line) if card == 9 && pcguard[0] == 0 if zeno == [] puts "皇帝のカードを使用しました" puts "山札にカードがない為、効果を発動しません" puts "----------------------------" else pchand[1] = zeno[0] zeno = zeno.delete_at(0) puts "皇帝のカードを使用しました" puts "相手がカードをドローし、 持っているカードはオープンします" puts "相手が持っていたのは#{pchand[0]}番と#{pchand[1]}番のカードです" puts "どちらのカードを指定しますか?" puts "あなたの手札にある現在のカードは#{myhand[0]}番のカードです" puts line input = gets.to_i pchand.delete_at(pchand.find_index(input)) puts "#{input}番のカードを捨てさせました" if input == 10 puts "英雄のカードは転生できない為、あなたの勝利です" exit end pcdiscard << input end elsif card == 8 && pcguard[0] == 0 #データ移動用 puts "精霊(交換)のカードを使用します" puts "自分の持っている#{myhand[0]}番のカードと相手の持っている#{pchand[0]}番のカードを交換しました" myhand[0], pchand[0] = pchand[0], myhand[0] elsif card == 7 puts "7番の賢者のカードを使用しました" puts "次のターンカードを3枚ドローし、一枚選択して手札に加えます" mywiseman[0] = 1 elsif card == 6 && pcguard[0] == 0 puts "あなたが貴族のカードを使用しました" puts "あなたが持っていたのは#{myhand[0]}番のカード" puts "相手が持っていたのは#{pchand[0]}番のカード" if myhand[0] == pchand[0] puts "持っていたカードが互角の為、相打ちです" elsif myhand[0] > pchand[0] puts "あなたの勝利です" else myhand[0] < pchand[0] puts "あなたの負けです" end exit elsif card == 5 && pcguard[0] == 0 puts "あなたが死神(疫病)のカードを使用しました" if zeno == [] puts "山札にカードがない為、死神の効果は発動しませんでした" else puts "死神の効果により、相手がカードを一枚ドローしました" pchand[1] = zeno[0] zeno.delete_at(0) # カードをシャッフルする pchand.shuffle #配列1番のカードを一枚捨てさせる input = pchand.shift if input == 10 puts "死神の効果により、相手の英雄のカードが墓地に送りました" puts "相手は英雄の効果により、転生札よりカードを引いて復活します" pchand[0] = hero[0] else puts "死神の効果により、相手の#{input}番のカードが墓地に送られました" end pcdiscard << input end elsif card == 4 puts "あなたが乙女(守護)のカードを使用しました" puts "次のターンまで相手の攻撃が無効化されます" myguard[0] = 1 elsif card == 3 && pcguard[0] == 0 puts "占い師のカードを使用しました" puts "相手が手に持っているカードをオープンします" puts "相手が持っていたのは#{pchand[0]}番のカードです" elsif card == 2 && pcguard[0] == 0 puts "兵士(捜査)のカードを使用しました" puts "自分の使用済みカード" mydiscard.each do |mydis| puts "#{mydis}番" end puts "相手の使用済みカード" if pcdiscard == [] puts "ありません" else pcdiscard.each do |pcdis| puts "#{pcdis}番" end end puts "あなたがいま手に持っているカード" puts "#{myhand[0]}番" puts "相手が持っていると思うカード番号を指定してください" input = gets.to_i while input < 1 || input > 10 puts "その番号は指定できません" puts "1から10までの番号を指定してください" input = gets.to_i end if input == pchand[0] && input == 10 puts "相手が持っていたのは英雄のカードでした" puts "英雄のカードの効果により、手札を捨てて転生します" pcdiscard << pchand[0] pchand[0] = hero[0] elsif input == pchand[0] puts "相手が持っていたのは #{pchand[0]}番のカードでした" puts "あなたの勝利です" exit else puts "相手「違います」" end elsif card == 1 && pcguard[0] == 0 puts "少年のカードを使用しました" myboy = mydiscard + pcdiscard if myboy.count(1) == 2 puts "少年のカードが使われたのは2枚目のため、効果を発動します" pchand[1] = zeno[0] zeno.delete_at(0) puts "相手がカードをドローし、持っているカードはオープンします" puts "持っていたのは#{pchand[0]}番と#{pchand[1]}番のカードです" puts "どちらのカードを指定しますか?" input = gets.to_i while !pchand.any?(input) do puts "その番号のカードは相手の手札にありません" puts "相手が持っているのは#{pchand[0]}番と#{pchand[1]}番のカードです" puts "もう一度番号を指定してください" input = gets.to_i end pchand.delete_at(pchand.find_index(input)) pcdiscard << input puts "#{input}番のカードを捨てさせました" if input == 10 puts "英雄の効果の発動により、手札を捨てて転生します" pchand[0] = hero[0] end else puts "少年のカードは初めて使われた為、何もおきません" end elsif pcguard[0] == 1 puts "相手の乙女(守護)のカードの効果により無効化されました" else puts "エラーです" end endこちらが相手ターンのメソッドです。引数の数がえげつない。
pctern.rbdef pctern(myhand, pchand, zeno, hero, mydiscard, pcdiscard, mywiseman, pcwiseman, myguard, pcguard, line) #相手がカードを引こうとして山札にカードがなかった場合、数字の大きさで勝敗を決する。 if zeno == [] decknone(mayhand, pchand) end #相手が前のターンに賢者を使った場合カードを3ドローする。zeno.firstでデッキの枚数が少なくてもエラーにならない if pcwiseman[0] == 1 pcwiseman[0] = 0 wiseman = zeno.first(3) pchand[1] = wiseman.max zeno.delete_at(zeno.find_index(wiseman.max)) zeno.shuffle puts "相手は賢者(選択)のカードの効果により、カードをドローしました" puts "カードを一枚選んで残りを山札に戻し、シャッフルしました" puts line end if pchand.length == 1 pchand[1] = zeno[0] zeno.delete_at(0) puts "相手がカードを一枚ドローしました" end input = pchand.min pchand.delete_at(pchand.find_index(input)) pcdiscard << input if input == 1 && myguard[0] == 0 pcboy = mydiscard + pcdiscard if pcboy.count(1) == 2 puts "相手が少年(革命)のカードを使用しました" puts "少年のカードが使われたのは二枚目です" if zeno == [] puts "山札が0枚の為、効果はは発動しませんでした" else puts "少年のカードの効果により、公開処刑を発動します" myhand[1] = zeno[0] zeno.delete_at(0) puts "少年のカードの効果により、#{myhand[1]}番のカードをドローしました" input = myhand.max myhand.delete_at(myhand.find_index(input)) puts "相手があなたの手札から#{input}番のカードを墓地へ送りました" if input == 10 puts "英雄のカードの効果により転生します" myhand[0] = hero[0] end end else puts "相手が少年のカードを使用しました" puts "少年のカードが使われたのは1枚目の為、何もおきません" end elsif input == 2 && myguard[0] == 0 pcsoldier = zeno + myhand sample = pcsoldier.sample puts "相手が兵士のカードを使用しました" puts "番号を宣言します" puts "相手「#{sample}番」" if myhand.any?(sample) if myhand[0] == 10 puts "あなたの持っていた英雄のカードが墓地にいきました" puts "英雄の効果により、転生札よりカードを引いて復活します" mydiscard << myhand[0] myhand[0] = hero[0] else puts "あなたの手札にあったのは#{myhand[0]}番のカードです" puts "あなたの負けです" exit end else puts "あなた「違います」" end elsif input == 3 && myguard[0] == 0 puts "相手が占い師のカードを使用しました" puts "相手があなたのカード番号が#{myhand[0]}番であることを確認しました" elsif input == 4 pcguard[0] = 1 puts "相手が乙女(守護)のカードを使用しました" puts "次のターンまであなたの攻撃が無効化されます" elsif input == 5 && myguard[0] == 0 puts "相手が死神(疫病)のカードを使用しました" if zeno == [] puts "デッキにカードがなかった為、何も起きません" end myhand[1] = zeno[0] zeno.delete_at(0) myhand.shuffle input = myhand.shift mydiscard << input if input == 10 puts "相手が英雄のカードが墓地に送られました" puts "英雄のカードの効果で転生します" myhand[0] = hero[0] else puts "あなたの手札の#{input}番のカードが墓地に送られました" end elsif input == 6 && myguard[0] == 0 puts "相手が貴族のカードを使用しました" puts "あなたが持っていたのは#{myhand[0]}番のカード" puts "相手が持っていたのは#{pchand[0]}番のカード" if myhand[0] == pchand[0] puts "持っていたカードが互角の為、相打ちです" elsif myhand[0] > pchand[0] puts "あなたの勝利です" else myhand[0] < pchand[0] puts "あなたの負けです" end exit elsif input == 7 puts "相手が賢者(選択)のカードを使用しました" puts "相手は次のターンにカードを3枚ドローし、一枚を選択して手札に加えます" pcwiseman[0] = 1 elsif input == 8 && myguard[0] == 0 puts "相手が精霊(交換)のカードを使用しました" puts "あなたの持っている#{myhand[0]}番のカードと、相手の持っている#{pchand[0]}番のカードを交換します" myhand[0], pchand[0] = pchand[0], myhand[0] elsif input == 9 && myguard[0] == 0 puts "相手が皇帝(公開処刑)のカードを使用しました" if zeno == [] puts "デッキにカードがなかった為、何も起きません" else myhand[1] = zeno[0] zeno.delete_at(0) input = myhand.delete_at(myhand.find_index(myhand.max)) pcdiscard << input if input == 10 puts "相手があなたの持っていた英雄のカードを墓地に送りました" puts "あなたの負けです" exit else puts "相手があなたの持っていた#{input}番のカードを墓地に送りました" end end elsif myguard[0] == 1 puts "乙女(守護の)効果により、相手の#{input}番のカードの効果を無効化しました" else puts "エラーです" end end山札にカードがなくなった場合のメソッドです。
これで決着です。decknone.rbdef decknone(myhand, pchand) puts "山札のカードがなくなりました" puts "お互いの持っているカードをオープンします" puts "あなたの持っているカードは#{myhand[0]}です" puts "相手が持っているカードは#{pchand[0]} です" if myhand[0] == pchand[0] puts "持っていたカードが同じの為、相打ちです" elsif myhand[0] > pchand[0] puts "あなたの勝ちです" else myhand[0] < pchand[0] puts "あなたの負けです" end exit endチュートリアルです。長すぎるので分けてあります。
tutorial.rbdef tutorial(line) puts "① 少年(革命)" puts "1枚目では何も起きない。2枚目が場に出された時皇帝と同じ効果を発動する" puts "ただし、少年は剣の扱いが未熟な為、英雄を殺すことはできない" puts line puts "② 兵士(捜査)" puts "カードの番号を指定する" puts "指定した番号を相手が持っていた場合、自分が勝利する" puts line puts "③ 占い師(透視)" puts "相手のカードを見ることができる" puts line puts "④ 乙女(守護)" puts "次の自分のターンがくるまで相手の攻撃を無効化する" puts line puts "⑤ 死神(疫病)" puts "相手はカードを一枚ドローする" puts "その後ランダムに一枚カードを捨てさせる" puts line puts "⑥ 貴族(対決)" puts "自分の持っているカードと相手の持っているカードをオープンする" puts "持っていたカードが大きい方が勝利" puts line puts "⑦ 賢者(選択)" puts "山札からカードを3枚ドローする" puts "指定したカードを一枚手札に加える" puts line puts "⑧ 精霊(交換)" puts "自分と相手の持っているカードを交換する" puts line puts "⑨ 皇帝(公開処刑)" puts "相手はカードを一枚ドローし、カードをオープンする" puts "指定した番号のカードを一枚捨てさせる" puts "相手が英雄のカードを持っていた場合自分の勝利" puts line puts "⑩ 英雄(潜伏・転生)" puts "手札から場に出すことのできないカード" puts "少年、兵士、死神で殺されても転生ふだで復活する" puts "ただし皇帝の劔で殺された場合は復活できず負ける" puts line end以上、また改善します。
- 投稿日:2020-04-01T22:22:25+09:00
【Ruby】カードゲームZENOをコンピューターと対戦して遊べるアプリ
カードゲームZENOをターミナル上で、コンピューターと対戦して遊べるアプリを作りました。
制作期間は5日間です。
ZENOをご存じないかたのために説明しますと、オリエンタルラジオの中田敦彦さんがプロデュースした新しいカードゲームです。UNOの売り上げをこえたことや、YOUTUBE上でメンタリストのDAIGOさんと白熱した心理戦を繰り広げられたことで話題になりました。
もともとはラブレターというカードゲームを題材として作られており、そこに中田敦彦さんが新たに手を加えて二ヶ月ほどで世界観や設定を作り上げました。AMAZONで770円ほどで購入することができます。
あまり興味がない方もいるかと思いますが、中田敦彦さんはDAIGOさん以外にも、ホリエモン、YOUTUBEのヒカルさん、クイズ王の伊沢拓司とも白熱した戦いをYOUTUBE上で公開してますので、そちらだけでもぜひ見てみてください。
現在就活中ですので、今後WEB上でプレイできるようにしたり、コードもリファクタリングする予定です。
友人への閲覧用にコードを公開してあります。
zeno.rbrequire './decknone' require './mytern' require './pctern' require './tutorial' line = "------------------------------------------" zeno = [10,9,8,8,7,7,6,6,5,5,4,4,3,3,2,2,1,1] zeno = zeno.shuffle #自分の手札カード myhand = [] myhand[0] = zeno[0] zeno.delete_at(0) #相手の手札カード pchand = [] pchand[0] = zeno[0] zeno.delete_at(0) #山札の一番下にあるヒーローカード hero = [] hero[0] = zeno[0] zeno.delete_at(0) #自分の使用済みカード mydiscard = [] #相手の使用済みカード pcdiscard = [] #自分が賢者を使っているかを判定 mywiseman = [] mywiseman[0] = 0 #相手が賢者を使っているかを判定 pcwiseman = [] pcwiseman[0] = 0 #自分が乙女を使っているかを判定 myguard = [] myguard[0] = 0 #相手が乙女を使っているかを判定 pcguard = [] pcguard[0] = 0 #先行か後攻かをランダムで決めるメソッドを作る。 def choice(myhand, line) puts "あなたの手札にあるのは#{myhand[0]}番と#{myhand[1]}番のカードです" puts "どちらのカードを使用しますか" puts "#{myhand[0]}番のカードを使用する" puts "#{myhand[1]}番のカードを使用する" puts "0番:チュートリアルを閲覧する" puts "11番:使用済みカードを確認する" puts line input = gets.to_i return input end puts "あなたが先行です" while true #デッキにカードがなかった場合、互いの数字の大きさで勝敗を決める。 if zeno == [] decknone(myhand, pchand) end #前回選んだカードが7番だった場合、賢者の効果を発動する。 if mywiseman[0] == 1 mywiseman[0] = 0 wiseman = zeno.first(3) puts "賢者のカードの効果により、カードをドローします" wiseman.each do |wise| puts "#{wise}番" end puts "あなたの現在の手札にあるのは#{myhand[0]}番のカードです" puts "どのカードを手札に加えますか" puts line input = gets.to_i while !wiseman.any?(input) do puts "そのカード番号は引いたカードの中にありません" wiseman.each do |wise| puts "#{wise}番" end puts "再度番号を入力してください" input = gets.to_i end myhand[1] = input zeno.delete_at(zeno.find_index(input)) zeno.shuffle puts "#{myhand[1]}番のカードを手札に加えました" puts "残りのカードを山札に戻してシャッフルします" end #自分がカードを一枚ドローする。先行か後攻かを決めるメソッドをあとで作る if myhand.length == 1 myhand[1] = zeno[0] zeno.delete_at(0) puts "あなたはカードをドローしました" puts "引いたのは#{myhand[1]}番のカードです" puts line end #カード選択画面を表示 input = choice(myhand, line) #使用できるカードがない場合は、こちらのメソッドにループする。 while (input != myhand[0] && input != myhand[1]) || input == 10 || input == 11 do if input == 0 tutorial(line) input = choice(myhand, line) elsif input == 10 puts line puts "英雄のカードは手札から使用することができません" input = choice(myhand, line) elsif input == 11 puts "自分の使用済みカード" if mydiscard == [] puts "ありません" else mydiscard.each do |mydis| puts "#{mydis}番" end end puts "相手の使用済みカード" if pcdiscard == [] puts "ありません" else pcdiscard.each do |pcdis| puts "#{pcdis}番" end end input = choice(myhand, line) else puts "その番号のカードは手札にありません" input= choice(myhand, line) end end #手札のカードを消去。墓地にカードを送る。 myhand.delete_at(myhand.find_index(input)) mydiscard << input #選んだカードの番号によって分岐が別れる mytern(myhand, pchand, input, zeno, hero, mydiscard, pcdiscard, mywiseman, pcwiseman, myguard, pcguard, line) pcguard[0] = 0 puts line pctern(myhand, pchand, zeno, hero, mydiscard, pcdiscard, mywiseman, pcwiseman, myguard, pcguard, line) myguard[0] = 0 puts line end自分のターンのメソッドはこちらに分けました。
mytern.rbdef mytern(myhand, pchand, card, zeno, hero, mydiscard, pcdiscard, mywiseman, pcwiseman, myguard, pcguard, line) if card == 9 && pcguard[0] == 0 puts "皇帝のカードを使用しました" if zeno == [] puts "山札にカードがない為、効果を発動しません" puts "----------------------------" else pchand[1] = zeno[0] zeno = zeno.delete_at(0) puts "相手がカードをドローし、持っているカードはオープンします" puts "相手が持っていたのは#{pchand[0]}番と#{pchand[1]}番のカードです" puts "どちらのカードを指定しますか?" puts "あなたの手札にある現在のカードは#{myhand[0]}番のカードです" puts line input = gets.to_i pchand.delete_at(pchand.find_index(input)) puts "#{input}番のカードを捨てさせました" if input == 10 puts "英雄のカードは転生できない為、あなたの勝利です" exit end pcdiscard << input end elsif card == 8 && pcguard[0] == 0 #データ移動用 puts "精霊(交換)のカードを使用します" puts "自分の持っている#{myhand[0]}番のカードと相手の持っている#{pchand[0]}番のカードを交換しました" myhand[0], pchand[0] = pchand[0], myhand[0] elsif card == 7 puts "7番の賢者のカードを使用しました" puts "次のターンカードを3枚ドローし、一枚選択して手札に加えます" mywiseman[0] = 1 elsif card == 6 && pcguard[0] == 0 puts "あなたが貴族のカードを使用しました" puts "あなたが持っていたのは#{myhand[0]}番のカード" puts "相手が持っていたのは#{pchand[0]}番のカード" if myhand[0] == pchand[0] puts "持っていたカードが互角の為、相打ちです" elsif myhand[0] > pchand[0] puts "あなたの勝利です" else myhand[0] < pchand[0] puts "あなたの負けです" end exit elsif card == 5 && pcguard[0] == 0 puts "あなたが死神(疫病)のカードを使用しました" if zeno == [] puts "山札にカードがない為、死神の効果は発動しませんでした" else puts "死神の効果により、相手がカードを一枚ドローしました" pchand[1] = zeno[0] zeno.delete_at(0) # カードをシャッフルする pchand.shuffle #配列1番のカードを一枚捨てさせる input = pchand.shift if input == 10 puts "死神の効果により、相手の英雄のカードが墓地に送りました" puts "相手は英雄の効果により、転生札よりカードを引いて復活します" pchand[0] = hero[0] else puts "死神の効果により、相手の#{input}番のカードが墓地に送られました" end pcdiscard << input end elsif card == 4 puts "あなたが乙女(守護)のカードを使用しました" puts "次のターンまで相手の攻撃が無効化されます" myguard[0] = 1 elsif card == 3 && pcguard[0] == 0 puts "占い師のカードを使用しました" puts "相手が手に持っているカードをオープンします" puts "相手が持っていたのは#{pchand[0]}番のカードです" elsif card == 2 && pcguard[0] == 0 puts "兵士(捜査)のカードを使用しました" puts "自分の使用済みカード" mydiscard.each do |mydis| puts "#{mydis}番" end puts "相手の使用済みカード" if pcdiscard == [] puts "ありません" else pcdiscard.each do |pcdis| puts "#{pcdis}番" end end puts "あなたがいま手に持っているカード" puts "#{myhand[0]}番" puts "相手が持っていると思うカード番号を指定してください" input = gets.to_i while input < 1 || input > 10 puts "その番号は指定できません" puts "1から10までの番号を指定してください" input = gets.to_i end if input == pchand[0] && input == 10 puts "相手が持っていたのは英雄のカードでした" puts "英雄のカードの効果により、手札を捨てて転生します" pcdiscard << pchand[0] pchand[0] = hero[0] elsif input == pchand[0] puts "相手が持っていたのは #{pchand[0]}番のカードでした" puts "あなたの勝利です" exit else puts "相手「違います」" end elsif card == 1 && pcguard[0] == 0 puts "少年のカードを使用しました" myboy = mydiscard + pcdiscard if myboy.count(1) == 2 puts "少年のカードが使われたのは2枚目のため、効果を発動します" pchand[1] = zeno[0] zeno.delete_at(0) puts "相手がカードをドローし、持っているカードはオープンします" puts "持っていたのは#{pchand[0]}番と#{pchand[1]}番のカードです" puts "どちらのカードを指定しますか?" input = gets.to_i while !pchand.any?(input) do puts "その番号のカードは相手の手札にありません" puts "相手が持っているのは#{pchand[0]}番と#{pchand[1]}番のカードです" puts "もう一度番号を指定してください" input = gets.to_i end pchand.delete_at(pchand.find_index(input)) pcdiscard << input puts "#{input}番のカードを捨てさせました" if input == 10 puts "英雄の効果の発動により、手札を捨てて転生します" pchand[0] = hero[0] end else puts "少年のカードは初めて使われた為、何もおきません" end elsif pcguard[0] == 1 puts "相手の乙女(守護)のカードの効果により無効化されました" else puts "エラーです" end endこちらが相手ターンのメソッドです。
pctern.rbdef pctern(myhand, pchand, zeno, hero, mydiscard, pcdiscard, mywiseman, pcwiseman, myguard, pcguard, line) #相手がカードを引こうとして山札にカードがなかった場合、数字の大きさで勝敗を決する。 if zeno == [] decknone(myhand, pchand) end #相手が前のターンに賢者を使った場合カードを3ドローする。zeno.firstでデッキの枚数が少なくてもエラーにならない if pcwiseman[0] == 1 pcwiseman[0] = 0 wiseman = zeno.first(3) pchand[1] = wiseman.max zeno.delete_at(zeno.find_index(wiseman.max)) zeno.shuffle puts "相手は賢者(選択)のカードの効果により、カードをドローしました" puts "カードを一枚選んで残りを山札に戻し、シャッフルしました" puts line end if pchand.length == 1 pchand[1] = zeno[0] zeno.delete_at(0) puts "相手がカードを一枚ドローしました" end input = pchand.min pchand.delete_at(pchand.find_index(input)) pcdiscard << input if input == 1 && myguard[0] == 0 pcboy = mydiscard + pcdiscard if pcboy.count(1) == 2 puts "相手が少年(革命)のカードを使用しました" puts "少年のカードが使われたのは二枚目です" if zeno == [] puts "山札が0枚の為、効果はは発動しませんでした" else puts "少年のカードの効果により、公開処刑を発動します" myhand[1] = zeno[0] zeno.delete_at(0) puts "少年のカードの効果により、#{myhand[1]}番のカードをドローしました" input = myhand.max myhand.delete_at(myhand.find_index(input)) puts "相手があなたの手札から#{input}番のカードを墓地へ送りました" if input == 10 puts "英雄のカードの効果により転生します" myhand[0] = hero[0] end end else puts "相手が少年のカードを使用しました" puts "少年のカードが使われたのは1枚目の為、何もおきません" end elsif input == 2 && myguard[0] == 0 pcsoldier = zeno + myhand sample = pcsoldier.sample puts "相手が兵士のカードを使用しました" puts "番号を宣言します" puts "相手「#{sample}番」" if myhand.any?(sample) if myhand[0] == 10 puts "あなたの持っていた英雄のカードが墓地にいきました" puts "英雄の効果により、転生札よりカードを引いて復活します" mydiscard << myhand[0] myhand[0] = hero[0] else puts "あなたの手札にあったのは#{myhand[0]}番のカードです" puts "あなたの負けです" exit end else puts "あなた「違います」" end elsif input == 3 && myguard[0] == 0 puts "相手が占い師のカードを使用しました" puts "相手があなたのカード番号が#{myhand[0]}番であることを確認しました" elsif input == 4 pcguard[0] = 1 puts "相手が乙女(守護)のカードを使用しました" puts "次のターンまであなたの攻撃が無効化されます" elsif input == 5 && myguard[0] == 0 puts "相手が死神(疫病)のカードを使用しました" if zeno == [] puts "デッキにカードがなかった為、何も起きません" end myhand[1] = zeno[0] zeno.delete_at(0) myhand.shuffle input = myhand.shift mydiscard << input if input == 10 puts "相手が英雄のカードが墓地に送られました" puts "英雄のカードの効果で転生します" myhand[0] = hero[0] else puts "あなたの手札の#{input}番のカードが墓地に送られました" end elsif input == 6 && myguard[0] == 0 puts "相手が貴族のカードを使用しました" puts "あなたが持っていたのは#{myhand[0]}番のカード" puts "相手が持っていたのは#{pchand[0]}番のカード" if myhand[0] == pchand[0] puts "持っていたカードが互角の為、相打ちです" elsif myhand[0] > pchand[0] puts "あなたの勝利です" else myhand[0] < pchand[0] puts "あなたの負けです" end exit elsif input == 7 puts "相手が賢者(選択)のカードを使用しました" puts "相手は次のターンにカードを3枚ドローし、一枚を選択して手札に加えます" pcwiseman[0] = 1 elsif input == 8 && myguard[0] == 0 puts "相手が精霊(交換)のカードを使用しました" puts "あなたの持っている#{myhand[0]}番のカードと、相手の持っている#{pchand[0]}番のカードを交換します" myhand[0], pchand[0] = pchand[0], myhand[0] elsif input == 9 && myguard[0] == 0 puts "相手が皇帝(公開処刑)のカードを使用しました" if zeno == [] puts "デッキにカードがなかった為、何も起きません" else myhand[1] = zeno[0] zeno.delete_at(0) input = myhand.delete_at(myhand.find_index(myhand.max)) pcdiscard << input if input == 10 puts "相手があなたの持っていた英雄のカードを墓地に送りました" puts "あなたの負けです" exit else puts "相手があなたの持っていた#{input}番のカードを墓地に送りました" end end elsif myguard[0] == 1 puts "乙女(守護の)効果により、相手の#{input}番のカードの効果を無効化しました" else puts "エラーです" end end山札にカードがなくなった場合のメソッドです。
これで決着です。decknone.rbdef decknone(myhand, pchand) puts "山札のカードがなくなりました" puts "お互いの持っているカードをオープンします" puts "あなたの持っているカードは#{myhand[0]}番です" puts "相手が持っているカードは#{pchand[0]} 番です" if myhand[0] == pchand[0] puts "持っていたカードが同じの為、相打ちです" elsif myhand[0] > pchand[0] puts "あなたの勝ちです" else myhand[0] < pchand[0] puts "あなたの負けです" end exit endチュートリアルです。長すぎるので分けてあります。
tutorial.rbdef tutorial(line) puts "① 少年(革命)" puts "1枚目では何も起きない。2枚目が場に出された時、皇帝と同じ効果を発動する" puts "ただし、少年は剣の扱いが未熟な為、英雄を殺すことはできない" puts line puts "② 兵士(捜査)" puts "カードの番号を指定する" puts "指定した番号を相手が持っていた場合、自分が勝利する" puts line puts "③ 占い師(透視)" puts "相手のカードを見ることができる" puts line puts "④ 乙女(守護)" puts "次の自分のターンがくるまで相手の攻撃を無効化する" puts line puts "⑤ 死神(疫病)" puts "相手はカードを一枚ドローする" puts "その後ランダムに一枚カードを捨てさせる" puts line puts "⑥ 貴族(対決)" puts "自分の持っているカードと相手の持っているカードをオープンする" puts "持っていたカードが大きい方が勝利" puts line puts "⑦ 賢者(選択)" puts "山札からカードを3枚ドローする" puts "指定したカードを一枚手札に加える" puts line puts "⑧ 精霊(交換)" puts "自分と相手の持っているカードを交換する" puts line puts "⑨ 皇帝(公開処刑)" puts "相手はカードを一枚ドローし、カードをオープンする" puts "指定した番号のカードを一枚捨てさせる" puts "相手が英雄のカードを持っていた場合、自分の勝利" puts line puts "⑩ 英雄(潜伏・転生)" puts "手札から場に出すことのできないカード" puts "少年、兵士、死神で殺されても転生ふだで復活する" puts "ただし皇帝の剣で殺された場合は復活できず負ける" puts line end以上、また改善します。
- 投稿日:2020-04-01T22:13:59+09:00
ログイン機能つけてくよ。〜Railsチュートリアル8章〜
少しづつ難しくなってきました。今回はログイン機能についてまとめてみます。
足早に来てるけどこうして一個一個確実にアウトプットしてくよ。この章ではログイン・ログアウト機能を実装していきます。
ログインの仕組みはブラウザがログインを保持し、ユーザーによってブラウザが閉じられたら状態を破棄すること。
ログインしたユーザーだけがアクセスできるページや扱える機能を制御する。こうした制限や制御の仕組みを認可モデルという。セッション
HTTPは状態(state)がない(less)。つまりステートレスなプロトコルである。
HTTPによるリクエストの一つ一つはそれより前の情報を全く利用できません。
リクエストが終わると何もかも忘れてしまう健忘症的なプロトコルである。
つまりHTTP自体にはブラウザの別のページへ移動したときにユーザーのIDを保持しておく手段がない。
ユーザーログインが必要なApplicationではセッションと呼ばれる半永続的な接続をコンピュータ間に別途設定を行う。cookies
Railsでセッションを実装する方法として最も一般的なのはcookiesを使う方法である。cookiesとはユーザーのブラウザに保存される小さなテキストデータである。cookiesはあるページから別のページに移動したときも破棄されないのでここにIDなどの情報を保存できる。Applicationはcookies内のデータをデータベースから取り出すことができる。
今回はsessionというRailsのメソッドを使って一時セッションを作成する。この一時セッションはブラウザを閉じると自動的に終了する。
セッションをRESTfulなResourcesとしてモデリングできると。他のRESTfulリソースと統一的に理解できる。
UserリソースとSessionリソースの違い
Userリソース...バックエンドでUserモデルを介してデータベース上の永続的データにアクセスする
Sessionリソース...cookiesを保存場所として使う。
Sessionsコントローラ
Sessionsコントローラの特定のRESTアクションにそれぞれ対応付けしていく。フォームはnewアクションで処理。
sessionsリソースの追加get '/login', to: 'sessions#new' post '/login', to: 'sessions#create' delete '/logout', to: 'sessions#destroyログインフォーム
ログインフォームを整えていく。入力時に誤りがあった場合ログインページをもう一度表示しエラーメッセージを出力する。サインアップページではエラーメッセージに専用のパーシャルを使用したが今回はActive Recordからの自動生成ではないためフラッシュメッセージを使用する。
Sessionフォームにはモデルが存在しないため@userのようなインスタンス変数に相当するものもない。したがって新しいSessionフォームを作成する時はform_forヘルパーに追加の情報を独自に渡さなければならない。
<%= form_for(:session, url: login_path) do |f| %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.submit "Log in", class: "btn btn-primary" %> <% end %>ユーザーの検索と認証
ログイン機能を実装する際にはまず入力が無効な場合の処理を最初に行う。
SessionでのアクションはSessionコントローラへ定義する。
セッションの中にはemailとpasswordの情報が含まれている。def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) # ユーザーログイン後にユーザー情報のページにリダイレクトする else # エラーメッセージを作成する render 'new' end enduser = User.find_by(email: params[:session][:email].downcase)params[:session][:email]で、セッションの値を取り出し、メールアドレスには小文字しか入力できないため.downcaseで小文字に変換しています。
その後find_byメソッドで入力したemailをカラムに保存されたemailが一致した場合にユーザーに代入している。if user && user.authenticate(params[:session][:password])&&を使って、条件を2つ指定している。
・user: userの中身がデータベースの内容と一致しているかどうか。
・user.authenticate: userに代入されたレコードのパスワードがポストした値と一致しているか
パスワードが登録しているユーザー情報と一致していたらセッションにユーザーIDを登録している。フラッシュメッセージの表示
ログインが失敗した場合にエラーメッセージを実装していく。まずは統合テストを構築する。
$ rails generate integration_test users_loginテストコードの流れ
1.ログイン用のパスを開く
2.新しいセッションのフォームが正しく表示されたことを確認する
3.わざと無効なparamsハッシュを使ってセッション用パスにPOSTする
4.新しいセッションのフォームが再度表示され、フラッシュメッセージが追加されることを確認する
5.別のページ (Homeページなど) にいったん移動する
6.移動先のページでフラッシュメッセージが表示されていないことを確認する実際のテスト
require 'test_helper' class UsersLoginTest < ActionDispatch::IntegrationTest test "login with invalid information" do get login_path #1.ログイン用のパスを開く assert_template 'sessions/new' #2新しいセッションのフォームが正しく表示されたことを確認する post login_path, params: { session: { email: "", password: "" } } ↑#3わざと無効なparamsハッシュを使ってセッション用パスにPOSTする assert_template 'sessions/new' assert_not flash.empty? #4新しいセッションのフォームが再度表示され、フラッシュメッセージが追加されることを確認する get root_path #5のページ (Homeページなど) にいったん移動する assert flash.empty? #6フラッシュメッセージが表示されていないことを確認する end endログイン失敗時の正しい処理
class SessionsController < ApplicationController def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) # ユーザーログイン後にユーザー情報のページにリダイレクトする else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end失敗するテストをパスさせるには、flashをflash.nowに置き換える。後者は、レンダリングが終わっているページで特別にフラッシュメッセージを表示することができます。flashのメッセージとは異なり、flash.nowのメッセージはその後リクエストが発生したときに消滅します
ログイン
実際にログイン中の状態での有効な値の送信をフォームで正しく扱える用にする。cookiesを使った一時セッションでユーザーをログインできるようにする。
セッションを実装するには通常様々なコントローラやビューでおびただしい数のメソッドを定義する必要がある。Rubyのモジュール機能を使うとそうしたメソッドを一箇所にパッケージ化できる。Sessionsコントローラを生成した時点でSessionヘルパーモジュールも自動生成されている。Applicationコントローラにこのモジュールを読み込ませればどのコントローラでも使える用になる。class ApplicationController < ActionController::Base protect_from_forgery with: :exception include SessionsHelper endinclude...モジュールを呼ぶためのメソッド
log_inメソッド
railsで事前定義済みのSessionメソッドを使って単純なログインを行える用にする。
session[:user_id] = user.idこのコードを実行するとユーザーのブラウザ内の一時cookiesに暗号化済みのユーザーIDが自動で作成される。Sessionメソッドで作成した一時cookiesは一時的に暗号化されこのコードは保護される。攻撃者がたとえこの情報を盗み出すことができてもそれを使って本物のユーザーとしてログインすることはできない。
このセッションでlog_inというヘルパーメソッドを定義できたのでユーザーログインを行ってSessionのcreateアクションを完了し、ユーザーのプロフィールページにリダイレクトする準備ができる。
def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) log_in user redirect_to user else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end現在のユーザー
ユーザーIDを一時セッションの中に安全におけるようになったので今度はそのユーザーを別のページで取り出すことにする。そのためにはcurrent_userメソッドを定義して、SessionIDに対応するユーザー名をデータベースから取り出せるようにする。
なぜcurrent_userを定義するのか。find_byメソッドを使うということは、使う度にデータベースアクセスが発生する。
データベースに保存されているデータが多いほど検索時間かかるし、無駄にアクセス増やしたくない。
そのためUser.find_byの実行結果をインスタンス変数に保存しておく方法がRailsの慣習である。
よって@current_userへの代入を行う。module SessionsHelper # 渡されたユーザーでログインする def log_in(user) session[:user_id] = user.id end # 現在ログイン中のユーザーを返す (いる場合) def current_user if session[:user_id] @current_user ||= User.find_by(id: session[:user_id]) end end endレイアウトリンクを変更する。
レイアウトがユーザーがログインしているときとそうでないときとで変更できるようにする。まず統合テストを書いていく。# ユーザーがログインしていればtrue、その他ならfalseを返す def logged_in? !current_user.nil? end end<% if logged_in? %> <li><%= link_to "Users", '#' %></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown"> Account <b class="caret"></b> </a> <ul class="dropdown-menu"> <li><%= link_to "Profile", current_user %></li> <li><%= link_to "Settings", '#' %></li> <li class="divider"></li> <li> <%= link_to "Log out", logout_path, method: :delete %> </li> </ul> </li> <% else %> <li><%= link_to "Log in", login_path %></li> <% end %>レイアウトの変更をテストする
1.ログイン用のパスを開く
2.セッション用パスに有効な情報をpostする
3.ログイン用リンクが表示されなくなったことを確認する
4.ログアウト用リンクが表示されていることを確認する
5.プロフィール用リンクが表示されていることを確認する上記のテストを行う際にはテストユーザーの作成が必要。
Railsではこのようなテスト用データをfixtureで作成できる。testデータベースでtestに必要なデータを読み込んでおくことができる。# 渡された文字列のハッシュ値を返す def User.digest(string) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost BCrypt::Password.create(string, cost: cost) endユーザー登録中のログイン
class UsersController < ApplicationController def show @user = User.find(params[:id]) end def new @user = User.new end def create @user = User.new(user_params) if @user.save log_in @user flash[:success] = "Welcome to the Sample App!" redirect_to @user else render 'new' end end private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end endログアウト
アプリケーションで扱う認証モデルでは、ユーザーが明示的にログアウトするまではログイン状態を保てなくてはならない。
SessionsコントローラのアクションはRESTfulルールに従っている。newでログインページを表示し、createでログインを完了するといった具合です。セッションを破棄するdestroyアクションも、引き続き同じ要領で作成できる。
ログアウトメソッドの定義module SessionsHelper # 渡されたユーザーでログインする def log_in(user) session[:user_id] = user.id end . . . # 現在のユーザーをログアウトする def log_out session.delete(:user_id) @current_user = nil end endSessionを破棄する
class SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) log_in user redirect_to user else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end def destroy log_out redirect_to root_url end endlog_outメソッドは、Sessionsコントローラのdestroyアクションでも同様に使っていく。
ユーザーログアウトのtest
require 'test_helper' class UsersLoginTest < ActionDispatch::IntegrationTest . . . test "login with valid information followed by logout" do get login_path post login_path, params: { session: { email: @user.email, password: 'password' } } assert is_logged_in? assert_redirected_to @user follow_redirect! assert_template 'users/show' assert_select "a[href=?]", login_path, count: 0 assert_select "a[href=?]", logout_path assert_select "a[href=?]", user_path(@user) delete logout_path assert_not is_logged_in? assert_redirected_to root_url follow_redirect! assert_select "a[href=?]", login_path assert_select "a[href=?]", logout_path, count: 0 assert_select "a[href=?]", user_path(@user), count: 0 end endテストでis_logged_in?ヘルパーメソッドを利用できるようにしてあったおかげで、有効な情報をセッション用パスにpostした直後にassert is_logged_in?で簡単にtestできる。
今日はここまで
- 投稿日:2020-04-01T21:53:46+09:00
ほぼ未経験の僕、アプリ開発するってよ。[3ヶ月目] [ポートフォリオ公開]
はじめに
初めてのポートフォリオ、ついに完成しました!!!
ほぼ未経験の僕、アプリ開発するってよ。[1ヶ月目の学習記録]以降、更新が止まってましたが、その後もrailsの学習を継続して、なんとかポートフォリオを完成させることができました!
今回は空白の2ヶ月目も合わせて、どういった手順でここまで到達したのか、どのようなポートフォリオを作成したのかという2点についてお話します。・これからプログラミング学習を始める方
・転職、新卒入社目的でプログラミング学習されている方
には非常に参考になると思います。また、前回の記事をお読みになってない方は先に読まれることを推奨します。読まれた方がより鮮明にトレース出来るかと思うので。
では本編です!
ここまでの学習軌跡
僕が1ヶ月目のときに書いた記事
ほぼ未経験の僕、アプリ開発するってよ。[1ヶ月目の学習記録]
では、railsチュートリアルの2章まででした。
そこから、どのような軌跡を辿ってポートフォリオ完成まで至ったのか見ていきましょう!まずアウトラインです。
○2ヶ月目
railsチュートリアル
↓
現場rails○3ヶ月目
ポートフォリオのアイディアを捻出
↓
設計
↓
デザイン
↓
フロントエンドコーディング (HTML CSS JavaScript)
↓
バックエンドコーディング (Rails)
↓
AWSデプロイでは詳細に見ていきます。
○2ヶ月目
2ヶ月目の目標は「railsの基礎を固める」です!
そこでまずは、railsエンジニアの登竜門railsチュートリアルです。
その後、より高度な内容を学習するため「現場で使える Ruby on Rails 5速習実践ガイド」を学習しました。(教材等はAmazonのリンクを貼ってます。アフィリエイトはありません。以降のリンクも同様です)railsチュートリアル
総学習時間は35時間程度でした。
知らない概念や専門用語が連続して、かなりしんどかった記憶があります。
当時は自走力も低かったので、全ての概念を調べきることができず、所々飛ばしながらやりました。
到達度としては80%程度だと思います。
十分に可視化されている情報だとは思いますが、1周目で全てを理解しようとしなくていいと思います。
2週目で完璧に理解する。または、80%程度の理解で次のタスクに進むというのがおすすめです。
私は後者です。現場で使える Ruby on Rails 5速習実践ガイド
総学習時間は30時間程度でした。
とても丁寧に解説されていて、控えめに言ってすごく良かったです。
railsチュートリアルの内容の復習もでき、その上発展的な内容に挑戦できました。
Ajaxや共同開発、リファクタリング等、発展的な内容は盛りだくさんで勉強になることが多かったです。
特に、migrationをバージョンとして扱う考え方などは印象的でした。今までは細かいことを考えずに「えいやっ!」とマイグレーションを作成していたのを、反省するいい機会になりました。
2つめの教材として大変良かったと思います。2ヶ月目の学習はこんな感じでした!
ここまででrailsの基礎が十分に固まったので、いざポートフォリオを作成!3ヶ月目に進みます。
○3ヶ月目
ついにやってきました。3ヶ月目の目標は「ポートフォリオの作成」です。
学習時間は100時間。まるまる1ヶ月かかりました。
主な目的は、アウトプットをしてスキルアップすること、ポートフォリオで企業へのアピールが出来るようになることの2つです。制作フローは
ポートフォリオのアイディアを捻出
↓
(設計)
↓
デザイン
↓
フロントエンドコーディング (HTML CSS JavaScript)
↓
バックエンドコーディング (Rails)
↓
AWSデプロイです。
では細かく解説して参ります!
ポートフォリオのアイディアの捻出
メンターに助言をもらいながら、割とサクッと決まりました。
コンセプトは「2chのような質問掲示板」。
誰かにプログラミングについて質問が出来るサービスがいくつかありますが、どれも少し堅苦しいという印象があるんですよね。
もっと気軽に質問ができる場所、ただプログラミングについてだらだら話せる場所。
そういう居場所があってもいいなと思ったのです。
ということで、言っちゃうと6割くらいは2chのパクリです笑[コラム]アイディアを捻出するちょっとしたコツ
アイディアを捻出する方法は色々あると思うのですが、一番簡単なのは「自分が欲しいもの」じゃないでしょうか。日頃の生活の中で感じる「不便」とか結構いいヒントになる気がします。設計
設計についてはスクールのメンターにしてもらいました。
もちろん設計も自分でやった方が良いのでしょうが、学習の優先度的にコーディングすることが最重要であったため、ここはメンターの力を借りました。要望を上手いこと設計に落としてもらえました。デザイン
これは「figma」という無料のツールを用いて作成しました。
設計を元に、「どういうデザインにしたらUI,UXが向上するか」を常に考えながら作成しました。
またこの時点で、設計について違和感がある所や、疑問は全て質問して解決しました。○デザインをして良かったこと
・作成するアプリのイメージが鮮明になる
設計があるからと言って、急にコーディングするのはやっぱり難しいですよね。
どういうアプリなのかを具体的にイメージできた方がやっぱり、コーディングしやすいです。
どういう動きになるのか。それは実際にコーディング出来るのか。など考慮する良い機会になります。・デザイナーの気持ちになる
デザイナーさんからデザインをもらって、それをコーディングする。というのが一般的なwebエンジニアの仕事ですよね。実際に自分でデザインを作ってみないと、デザイナーの気持ちが理解できないと思うのです。デザイナーの気持ちになって、「デザインって作るの大変だな」「どうやったらコーディングしやすいデザインが作れるだろう」などと考えるには良い機会になります。フロントエンドコーディング
作成したデザインを元にHTML CSS JavaScriptでアプリの見た目を作成していきます。
デザインがあったので、サイトの模写コーディングと何ら変わり無かったです。
シンプルなデザインだったので、難しいことも特にありませんでした。
ただ一点、bootstrapを用いてほとんどのスタイルを書こうと思ってたのですが、実際に書いてみるとあまり融通がきかなかったので、結局ほとんど生のCSSで書きました。(勉強不足でただ不慣れだった説は否めません)バックエンドコーディング
ついに来ました。Railsのコーディング。
ここがメインです!一番時間をかけました。
ではアプリ作りましょう!と1から自分で書くとなると「あれ、何すればいいんだっけ?」てなります笑
rails new からつまづいたのは良い思い出です。
バージョン等の諸々の設定に、オプションを使います。でも、「あれどうやって指定するんだ?」「この設計の場合はこの設定する必要はあるのか?」など考慮することが多く、コマンド一つ馬鹿に出来ません。
今までも何も考えずに与えられたコードを入力してただけですが、一つ一つのコマンドやコードを吟味する必要が出てきて、とても良いトレーニングになりました。「ここにこだわったよ!」って所は、後にコードを見せながら詳細に解説します!
AWSデプロイ
プログラム自体が完成しても、それをサーバーにあげないともちろん誰にも使ってもらえません。
また本番環境で動作させてみると、開発環境では起きなかったエラーが発生する可能性もあります。
もちろんデプロイせずにgit hubに公開して完成としてもいいんですが、今回は勉強がてらAWSにデプロイしました。
世界一丁寧なAWS解説。EC2を利用して、RailsアプリをAWSにあげるまで
こちらのqiita記事を参考にしながらデプロイしました。
とても大変でした。。。
AWSの設定自体もそうですが、秘密鍵、公開鍵の設定など、普段触れない技術領域でした。
エンジニアは学習することが多いなと再認識しました。ポートフォリオ公開!
さあ、そうこうして完成したポートフォリオ、公開します!
ドン!!!
TechBoard
ドン!!!どうでしょうか?見ていただけましたかね。
もちろんまだプロトタイプなので改善点は多いのですが、それを気にしてたらいつまでも公開できないなと思ったので公開しちゃいます。
少しでも触って頂けたら泣いて喜びますここに注目!
プログラムを用いて簡単な解説をしようと思ったのですが、思ったよりもヴォリュームがあったので別記事におこします!
現在準備中ですm(. .)m
まとめ
再三言いますが、やはりエンジニアは学習することが多いですね。
エンジニアが触れる領域は、広くて深いです。
様々技術を活用して一つのサービスが形になっているというのがよくわかりました。
学習をだらだらやっていると、いつまで経っても一人前のエンジニアにはなれないと危機感を感じると共に、案外エンジニアは楽じゃないなと感じました。
と言いますのも、私は皆さんがよく知るようなインフルエンサーに煽られてプログラミングを始めた経緯があります。完全にエンジニアをなめてました笑同じくエンジニアを目指されている方に少しでも参考になれば幸いです!
- 投稿日:2020-04-01T21:28:12+09:00
Kinx ライブラリ - Signal
Zip
はじめに
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。言語はライブラリが命。ということでライブラリの使い方編。
今回は Zip です。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
アプリ作る時に Zip 機能は欠かせないですね。あと、脆弱だ、脆弱だ、言われてますけど、業務アプリの中ではパスワード付き Zip が(建前上)求められることも事実。なので、パスワード付き Zip が作れないと実用にならない(日本では...?)。
Zip
Zip アーカイブ作成
Zip インスタンスの作成(
class Zip
)基本的にはこんな感じ。
var zip = new Zip("zipfile.zip", File.READ|File.WRITE);ファイル名とモードを指定する。
モード
モード 意味 File.READ
読み込みモード File.WRITE
書き込みモード File.APPEND
追記モード File と同じ。
メソッド
Zip インスタンスのメソッドは以下の通り。
メソッド 内容 オプション extract(name, [opts])
展開して文字列で取得 { password, overwrite, skip, }
extractTo(name, file [, opts])
ファイルに展開 (同上) find(name)
エントリを検索、エントリ・オブジェクトを返す addFile(filename [, opts])
Zip ファイルにエントリを追加 { password, method, aes, level }
addString(text [, opts])
Zip ファイルにエントリを追加 (同上) setPassword(password)
全 Zip エントリで共通して使用するパスワードを設定 setOverwrite(truefalse)
上書き設定を一括で指定しておくために使用する エントリの追加(
addFile()
/addString()
)
addFile()
またはaddString()
を使います。即座にエントリが追加されます。zip.addFile("README.md");ファイルにディレクトリ名を与えた場合、ディレクトリは以下のファイルが追加される。また、第 2 引数にションを渡せる。オプションの内容は次の通り。
password
: パスワード付き Zip のパスワード。デフォルトは無し。method
: 圧縮方法。デフォルトはdeflate
。その他、指定できるのはstore
、bzip2
、lzma
。aes
: WinZIP 互換の AES 暗号化を有効にするか(true/false)。デフォルト false。尚、addString
の場合には無視される。level
: 圧縮レベル。0-9。暗号化
addFile()
の際にオプションで指定できるが、一括で最初に設定して置く場合はsetPassword()
を使うことができる。var zip = new Zip("zipfile.zip", File.READ|File.WRITE); zip.setPassword("password");Zip ファイル一覧の表示
zip
インスタンスには既に配列としてエントリ・オブジェクトが格納されている。以下のようにすると一覧表示することができる。尚、zip.totalFiles
にエントリ数が格納されている。var zip = new Zip("zipfile.zip", File.READ); System.println("totalFiles = ", zip.totalFiles); zip.each(function(e) { System.println("%s:" % e.filename); e.keySet().each(&(key) => { if (e[key].isFunction || e[key].isObject || e[key].isUndefined) { return; // 展開系の関数などはスキップ。 } if (key == "crc32") { // CRC は 16 進表示 System.println(" %-14s = %10X" % key % e[key]); } else if (key != "time" && key != "filename") { // 別で表示 System.println(" %-14s = %10d" % key % e[key]); } }); // time はさらにオブジェクト構造になっている e.time.keySet().each(&(k) => { System.println(" time.%-7d = %10d" % k % e.time[k]); }); // // エントリを個別に展開することも可能。 // if (e.filename == "README.md") { // e.extractTo("READMEXX.md", { password: "text", overwrite: true }); // } });以下のような感じで表示される。
totalFiles = 4 README.md: compsize = 4413 size = 11621 isDirectory = 0 crc32 = EFD9A09C isEncrypted = 1 method = deflate time.month = 3 time.minute = 1 time.day = 19 time.year = 120 time.second = 2 time.hour = 16 ...展開
Zip ファイルの展開は、以下の 2 通りの方法が可能。
- 直接 Zip インスタンスから展開する。
- Zip エントリオブジェクトから展開する。
Zip エントリから展開する場合は個別の展開になる。その場合、上記のようにイテレートして選択する方法と、
find
メソッドを使う方法の 2 種類がある。find
メソッドは、指定したファイル名のエントリがあれば Zip エントリオブジェクトを返す。展開時のオプションの意味は、以下の通り。
password
: 展開に使うパスワード。指定されなかった場合、setPassword()
で設定されたものを使う。setPassword()
でも設定されてなかった場合、パスワードなしで展開しようとする。overwrite
: true を指定し、同名ファイルが既に存在した場合、上書きする。skip
: true を指定し、同名ファイルが既に存在した場合、スキップする。尚、
overwrite
もskip
も指定されずに同名ファイルが存在した場合、ZipException
例外が送出される。すべて展開
すべて展開するには、上記イテレートしたエントリに対して
extractTo
を実施する。必要なディレクトリは自動的に作成される。zip.each(&(e) => e.extractTo("examples/zip/dst" / e.filename, { password: "text", skip: true }));なんか説明してなかった気がするが、文字列に対して
/
オペレータを適用すると、/
で連結された文字列になる。ファイルを指定して展開
直接 Zip インスタンスに対して
extract
またはextractTo
メソッドを使うことが可能。zip.extractTo("README.md", "READMEXX.md", { password: "text", skip: true });
extract
を使用した場合、展開した内容を文字列として返す。var text = zip.extract("README.md", { password: "text" });現在、バイナリで取得する方法が無いのに気がついたので、追加する予定。オプションに
{ binary: true }
をつけるイメージ。Zip エントリオブジェクトの場合、エントリ名を指定する引数がなくなる。
メソッド 内容 オプション extract([opts])
展開して文字列で取得 { password, overwrite, skip, }
extractTo(file [, opts])
ファイルに展開 (同上)
find
を使った例は以下の通り。zip.find("README.md") .extractTo("READMEXX.md", { password: "text", skip: true });var text = zip.find("README.md") .extract({ password: "text" });その他
使っているライブラリ
これです。
機能一覧。全然 Mini な感じがしないですね。
- Features
- Creating and extracting zip archives.
- Adding and removing entries from zip archives.
- Read and write raw zip entry data.
- Reading and writing zip archives from memory.
- Zlib, BZIP2, and LZMA compression methods.
- Password protection through Traditional PKWARE and WinZIP AES encryption.
- Buffered streaming for improved I/O performance.
- NTFS timestamp support for UTC last modified, last accessed, and creation dates.
- Disk split support for splitting zip archives into multiple files.
- Preservation of file attributes across file systems.
- Follow and store symbolic links.
- Unicode filename support through UTF-8 encoding.
- Legacy character encoding support CP437, CP932, CP936, CP950.
- Turn off compilation of compression, decompression, or encryption.
- Windows (Win32 & WinRT), macOS and Linux platform support.
- Streaming interface for easy implementation of additional platforms.
- Support for Apple's compression library ZLIB implementation.
- Zero out local file header information.
- Zip/unzip of central directory to reduce size.
- Ability to generate and verify CMS signature for each entry.
- Recover the central directory if it is corrupt or missing.
- Example minizip command line tool.
Zip64 対応
Zip64 も対応されている模様。4G 超えもいけるとの話だがテストできていない。
おわりに
Zip/Unzip はスクリプト言語を使う目的としては上位に来る機能でしょう。間違っても C で組みたいとは思わないし、簡単に Zip ファイル作りたい。
では、また次回。
- 投稿日:2020-04-01T21:28:12+09:00
Kinx ライブラリ - Zip
Zip
はじめに
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。言語はライブラリが命。ということでライブラリの使い方編。
今回は Zip です。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
アプリ作る時に Zip 機能は欠かせないですね。あと、脆弱だ、脆弱だ、言われてますけど、業務アプリの中ではパスワード付き Zip が(建前上)求められることも事実。なので、パスワード付き Zip が作れないと実用にならない(日本では...?)。
Zip
Zip アーカイブ作成
Zip インスタンスの作成(
class Zip
)基本的にはこんな感じ。
var zip = new Zip("zipfile.zip", File.READ|File.WRITE);ファイル名とモードを指定する。
モード
モード 意味 動作概要 File.READ
読み込みモード 単独で指定した場合、ファイルが存在しなければ ZipException
File.WRITE
書き込みモード ファイルが存在しても新規に作成しなおすモード File.APPEND
追記モード ファイルが存在した場合、そのファイルに追記するモード File と同じ。
メソッド
Zip インスタンスのメソッドは以下の通り。
メソッド 内容 オプション extract(name, [opts])
展開して文字列で取得 { password, overwrite, skip, }
extractTo(name, file [, opts])
ファイルに展開 (同上) find(name)
エントリを検索、エントリ・オブジェクトを返す addFile(filename [, opts])
Zip ファイルにエントリを追加 { password, method, aes, level }
addString(text [, opts])
Zip ファイルにエントリを追加 (同上) setPassword(password)
全 Zip エントリで共通して使用するパスワードを設定 setOverwrite(truefalse)
上書き設定を一括で指定しておくために使用する エントリの追加(
addFile()
/addString()
)
addFile()
またはaddString()
を使います。即座にエントリが追加される。zip.addFile("README.md");ファイルにディレクトリ名を与えた場合、ディレクトリは以下のファイルが追加される。また、第 2 引数にオプションを渡せる。オプションの内容は次の通り。
password
: パスワード付き Zip のパスワード。デフォルトは無し。method
: 圧縮方法。デフォルトはdeflate
。その他、指定できるのは"store"
、"bzip2"
、"lzma"
。aes
: WinZIP 互換の AES 暗号化を有効にするか(true/false)。デフォルト false。尚、addString
の場合には無視される。level
: 圧縮レベル。0-9。オプションを付ける例は以下の通り。
zip.addFile("README.md", { method: "bzip2", password: "password", aes: true, }); zip.addString("test/test1.txt", { content: "test/test\n", // aes: true, // addString では無視される. });パスワードは、展開の際に個別に指定するようにすればエントリごとに別々につけることもできる。
暗号化
addFile()
の際にオプションで指定できるが、一括で最初に設定して置く場合はsetPassword()
を使うことができる。var zip = new Zip("zipfile.zip", File.READ|File.WRITE); zip.setPassword("password");Zip ファイル一覧の表示
zip
インスタンスには既に配列としてエントリ・オブジェクトが格納されている。以下のようにすると一覧表示することができる。尚、zip.totalFiles
にエントリ数が格納されている。var zip = new Zip("zipfile.zip", File.READ); System.println("totalFiles = ", zip.totalFiles); zip.each(function(e) { System.println("%s:" % e.filename); e.keySet().each(&(key) => { if (e[key].isFunction || e[key].isObject || e[key].isUndefined) { return; // 展開系の関数などはスキップ。 } if (key == "crc32") { // CRC は 16 進表示 System.println(" %-14s = %10X" % key % e[key]); } else if (key != "time" && key != "filename") { // 別で表示 System.println(" %-14s = %10d" % key % e[key]); } }); // time はさらにオブジェクト構造になっている e.time.keySet().each(&(k) => { System.println(" time.%-7d = %10d" % k % e.time[k]); }); // // エントリを個別に展開することも可能。 // if (e.filename == "README.md") { // e.extractTo("READMEXX.md", { password: "text", overwrite: true }); // } });以下のような感じで表示される。
totalFiles = 4 README.md: compsize = 4413 size = 11621 isDirectory = 0 crc32 = EFD9A09C isEncrypted = 1 method = deflate time.month = 3 time.minute = 1 time.day = 19 time.year = 2020 time.second = 2 time.hour = 16 ...展開
Zip ファイルの展開は、以下の 2 通りの方法が可能。
- 直接 Zip インスタンスから展開する。
- Zip エントリオブジェクトから展開する。
Zip エントリから展開する場合は個別の展開になる。その場合、上記のようにイテレートして選択する方法と、
find
メソッドを使う方法の 2 種類がある。find
メソッドは、指定したファイル名のエントリがあれば Zip エントリオブジェクトを返す。展開時のオプションの意味は、以下の通り。
password
: 展開に使うパスワード。指定されなかった場合、setPassword()
で設定されたものを使う。setPassword()
でも設定されてなかった場合、パスワードなしで展開しようとする。overwrite
: true を指定し、同名ファイルが既に存在した場合、上書きする。skip
: true を指定し、同名ファイルが既に存在した場合、スキップする。尚、
overwrite
もskip
も指定されずに同名ファイルが存在した場合、ZipException
例外が送出される。すべて展開
すべて展開するには、上記イテレートしたエントリに対して
extractTo
を実施する。必要なディレクトリは自動的に作成される。zip.each(&(e) => e.extractTo("examples/zip/dst" / e.filename, { password: "text", skip: true }));なんか説明してなかった気がするが、文字列に対して
/
オペレータを適用すると、/
で連結された文字列になる。ファイルを指定して展開
直接 Zip インスタンスに対して
extract
またはextractTo
メソッドを使うことが可能。zip.extractTo("README.md", "READMEXX.md", { password: "text", skip: true });
extract
を使用した場合、展開した内容を文字列として返す。var text = zip.extract("README.md", { password: "text" });現在、バイナリで取得する方法が無いのに気がついたので、追加する予定。オプションに
{ binary: true }
をつけるイメージ。Zip エントリオブジェクトの場合、エントリ名を指定する引数がなくなる。
メソッド 内容 オプション extract([opts])
展開して文字列で取得 { password, overwrite, skip, }
extractTo(file [, opts])
ファイルに展開 (同上)
find
を使った例は以下の通り。zip.find("README.md") .extractTo("READMEXX.md", { password: "text", skip: true });var text = zip.find("README.md") .extract({ password: "text" });その他
使っているライブラリ
これです。
機能一覧。全然 Mini な感じがしないですね。
- Features
- Creating and extracting zip archives.
- Adding and removing entries from zip archives.
- Read and write raw zip entry data.
- Reading and writing zip archives from memory.
- Zlib, BZIP2, and LZMA compression methods.
- Password protection through Traditional PKWARE and WinZIP AES encryption.
- Buffered streaming for improved I/O performance.
- NTFS timestamp support for UTC last modified, last accessed, and creation dates.
- Disk split support for splitting zip archives into multiple files.
- Preservation of file attributes across file systems.
- Follow and store symbolic links.
- Unicode filename support through UTF-8 encoding.
- Legacy character encoding support CP437, CP932, CP936, CP950.
- Turn off compilation of compression, decompression, or encryption.
- Windows (Win32 & WinRT), macOS and Linux platform support.
- Streaming interface for easy implementation of additional platforms.
- Support for Apple's compression library ZLIB implementation.
- Zero out local file header information.
- Zip/unzip of central directory to reduce size.
- Ability to generate and verify CMS signature for each entry.
- Recover the central directory if it is corrupt or missing.
- Example minizip command line tool.
Zip64 対応
Zip64 も対応されている模様。4G 超えもいけるとの話だがテストできていない。
おわりに
Zip/Unzip はスクリプト言語を使う目的としては上位に来る機能でしょう。間違っても C で組みたいとは思わないし、簡単に Zip ファイル作りたい。
では、また次回。
- 投稿日:2020-04-01T20:34:18+09:00
フィボナッチ数列 Ruby Perl JavaScript
はじめに
フィボナッチ数列の記事ってフィボナッチ数列100番目より遅いところですが、勉強中のもっとプログラマ脳を鍛える数学パズル アルゴリズムが脳にしみ込む70問 のアウトプットということでお許しください。
Ruby
rubymemo.rb@memo = {0 => 1, 1 => 1} def f(n) return @memo[n] if @memo[n] @memo[n] = f(n - 1) + f(n - 2) end puts f(10) # => 89rubyarray.rbN = 10 f = Array.new() f[0] = f[1] = 1 2.upto(N) do |i| f[i] = f[i - 1] + f[i - 2] end puts f[N] # => 89javascript の様にハッシュではなく配列でも実装可能と思われます。
Perl
perlmemo.plmy %memo = (0 => 1, 1 => 1); sub f { my $n = shift; return $memo{$n} if defined $memo{$n}; $memo{$n} = f($n - 1) + f($n - 2); } print f(10), "\n"; # => 89perlarray.plmy $n = 10; my @f; $f[0] = $f[1] = 1; for my $i (2..$n) { $f[$i] = $f[$i - 1] + $f[$i -2]; } print $f[$n], "\n"; # => 89ruby_perl.plreturn @memo[n] if @memo[n] # ruby return $memo{$n} if defined $memo{$n}; # perl return $memo{$n} if exists $memo{$n}; # perlruby と異なり、defined関数もしくは exists関数を使用する必要があります。
JavaScript
javascriptmemo.jsvar memo = []; memo[0] = memo[1] = 1; function f(n) { if (memo[n]) return memo[n]; return memo[n] = f(n - 1) + f(n - 2); } console.log(f(10)); // => 89javascriptarray.jsN = 10; var f = []; f[0] = f[1] = 1; for (var i = 2; i <= N; i++) { f[i] = f[i - 1] + f[i - 2]; } console.log(f[N]); // => 89javascript に詳しくないので、ハッシュ未使用で実装しています。
まとめ
- フィボナッチ数列を実装した
- それぞれの言語に違いがあって面白かった
参照したサイト
フィボナッチ数列
メモ化から学ぶ早期リターン
javascriptのハッシュライブラリを比較する
exists関数 - ハッシュのキーの存在確認
- 投稿日:2020-04-01T19:47:18+09:00
Rails ウィザード形式導入について 2
はじめに
Rails ウィザード形式導入について 1 はこちらをクリック願います。
チーム開発でフリマサイトを開発致しました。
その際、ユーザーの新規登録画面でウィザード形式を導入致しましたので、内容を整理します。
もうすでにご存知の方、省略の仕方等ご存知でしたら、ご教授願います。前提
- ユーザー情報(User)については 以下 A と記述します。
- 住所情報(Destination)については 以下 B と記述します。
Aの新規登録のnewアクションとビュー
- app/controllers/users/registrations_controller.rbを見てみてください。
- えらいことになっているかと思います・・・
- なんだこれ・・・
app/controllers/users/registrations_controller.rb# frozen_string_literal: true class Users::RegistrationsController < Devise::RegistrationsController # before_action :configure_sign_up_params, only: [:create] # before_action :configure_account_update_params, only: [:update] # GET /resource/sign_up # def new # super # end # POST /resource # def create # super # end # GET /resource/edit # def edit # super # end # PUT /resource # def update # super # end # DELETE /resource # def destroy # super # end # GET /resource/cancel # Forces the session data which is usually expired after sign # in to be expired now. This is useful if the user wants to # cancel oauth signing in/up in the middle of the process, # removing all OAuth session data. # def cancel # super # end # protected # If you have extra params to permit, append them to the sanitizer. # def configure_sign_up_params # devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute]) # end # If you have extra params to permit, append them to the sanitizer. # def configure_account_update_params # devise_parameter_sanitizer.permit(:account_update, keys: [:attribute]) # end # The path used after sign up. # def after_sign_up_path_for(resource) # super(resource) # end # The path used after sign up for inactive accounts. # def after_inactive_sign_up_path_for(resource) # super(resource) # end end
- コメントアウトしている箇所はすでにDevise::RegistrationsControllerで定義されているものです。
- コメントアウト部分を外して上書きすることができます。(メソッドのオーバーライド)
- superはスーパークラス(今回であればDevise)のメソッドを呼び出しています。
とりあえずコメントアウト部分(superクラスの呼び出し部分)を全部消してしまいます・・・
- deviseとUserモデルが紐づくように設定してあるので、superで呼び出してもdeviseは同様の操作を行ってくれるようです。
- 勉強中ですので、super呼び出し方法についてはお待ちください(泣)
newアクションを定義する
app/controllers/users/registrations_controller.rbclass Users::RegistrationsController < Devise::RegistrationsController def new @user = User.new end endそして、対応しているビューも編集する
- 参考程度にみてください。このまま入力するとエラーが出るかも・・・です。
app/views/devise/registrations/new.html.haml.main .title .title__font A情報入力 = form_for(@user, url: user_registration_path) do |f| .name-information .name-information__item .name-information__item__nicname = f.label :name,"ニックネーム" .name-information__name = f.text_field :name,size:26 .email .email__information .email__information__address = f.label :email,"メールアドレス" .email__information = f.email_field :email, autofocus: true, autocomplete: "email",size:26 .password .password__item .password__item__pass = f.label :password,"パスワード" .password__item__note - if @minimum_password_length (#{@minimum_password_length} 文字以上必要です) .password__input .password__description = f.password_field :password, autocomplete: "new-password", size:26 .re-enter .re-enter__item .re-enter__item__pass = f.label :password_confirmation,"確認用パスワード " .re-enter__itempass = f.password_field :password_confirmation, autocomplete: "new-password",size:26 .terms-of-service .terms-of-service__btm %input#submit_button1{:name => "submit", :type => "submit", :value => "次へ進む"}/Aの新規登録のcreateアクションとビュー
- 1ページ目で入力した情報のバリデーションチェックをします。
- 1ページで入力した情報をsessionに保持させます。
- 次のBの登録で使用するインスタンスを生成、当該ページへ遷移するようにします。
createアクションを定義する(追記してください)
app/controllers/users/registrations_controller.rbclass Users::RegistrationsController < Devise::RegistrationsController before_action :configure_sign_up_params, only: [:create] # 省略 def create @user = User.new(sign_up_params) unless @user.valid? flash.now[:alert] = @user.errors.full_messages render :new and return end session["devise.regist_data"] = {user: @user.attributes} session["devise.regist_data"][:user]["password"] = params[:user][:password] @destination = @user.build_destination render :new_destination end protected def configure_sign_up_params devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute]) end end1ページ目で入力した情報のバリデーションチェック
- Userモデルのインスタンスを生成する。
- 1ページ目から送られてきたパラメータをインスタンス変数@userに代入する。
- そのインスタンス変数に対してvalid?メソッドを適用する。
- 送られてきたパラメータが指定されたバリデーションに違反しないかどうかチェックすることができる。
- falseになった場合は、エラーメッセージとともにnewアクションへrenderする。
1ページで入力した情報をsessionに保持させる
- A情報だけではなく、B情報までページ遷移した後に保存するという機能を達成するのが目的です。
- そのために、sessionという機能を用いる。
- sessionとは、ページが遷移しても情報が消えることが無いように、クライアント側で保持をさせておく機能のことをいうようです。
- A情報のバリデーションチェック後、session["devise.regist_data"]に値を代入する。
- この時、sessionにハッシュオブジェクトの形で情報を保持させるために、attributesメソッドを用いてデータを生成する。
- また、paramsの中にはパスワードの情報は含まれているが、attributesメソッドでデータ整形をした際にパスワードの情報は含まれない。
- そこで、パスワードを再度sessionに代入する。
B情報登録で使用するインスタンスを生成、B情報登録ページへ遷移する
- 次ページで、ユーザーモデルに紐づくB情報を入力させるため、該当するインスタンスを生成しておく必要がある。
- そのために、build_destinationで今回生成したインスタンス@userに紐づくDestinationモデルのインスタンスを生成する。
- ここで生成したDestinationモデルのインスタンスは、@destinationというインスタンス変数に代入する。
- そして、B情報を登録させるページを表示するnew_destinationアクションのビューへrenderする。
難しい!でも自作アプリには入れたいです!もうひと頑張り!
続きは次回!
さいごに
日々勉強中ですので、随時更新します。
皆様の復習にご活用頂けますと幸いです。
- 投稿日:2020-04-01T19:15:55+09:00
strftimeとは?
はじめに
新しい会社でインターンをはじめ、最初の方に回ってきた保守開発で、複数の日時を比べる機会がありました。
そこで、いろいろ調べてこのstrftimeを学んだので、アウトプットをかねて記事を書いていきます!(自分の初めてのqiita記事です)strftimeとは?
rubyのstrftimeリファレンスは
時刻を format 文字列に従って文字列に変換した結果を返す
と言っています。そもそも日時ってどうやって取り出すの?
めっちゃいい記事を見つけたのでこちらを参照してください笑
RubyとRailsにおけるTime, Date, DateTime, TimeWithZoneの違い
Time.nowで現在の日時を取得できます。
irb(main):001:0> Time.now
=> 2020-04-01 18:42:47 +0900
今回は、Time.nowからstrftimeを用いて値を呼び出したい形に変える方法についてまとめます。strftimeを使ってみよう!
まずは、rubyのstrftimeリファレンスにある表を眺めて概要を理解しましょう!
この表からいくつか抜粋して、実際の開発や業務で役に立ちそうなものを、コードを用いて紹介していきます!できること1 曜日の取得 %A,%a
irb(main):008:0> t = Time.now
=> 2020-04-01 18:59:14 +0900
irb(main):009:0> t.strftime("%A")
=> "Wednesday"
irb(main):010:0> t.strftime("%a")
=> "Wed"これ以下はtに現在時刻が代入されているものとする
できること2 月の取得 %B,%b
irb(main):011:0> t.strftime("%B")
=> "April"
irb(main):012:0> t.strftime("%b")
=> "Apr"できること3 日時と日付 %c
irb(main):013:0> t.strftime("%c")
=> "Wed Apr 1 18:59:14 2020"できること4 日付の取得 %y/%m/%d
irb(main):016:0> t.strftime("%y/%m/%d")
=> "20/04/01"
irb(main):019:0> t.strftime("%Y/%m/%d")
=> "2020/04/01"
irb(main):018:0> t.strftime("%Y%m%d")
=> "20200401"できること5 日付、それぞれの取得
irb(main):021:0> t.strftime("%Y")
=> "2020"
irb(main):022:0> t.strftime("%m")
=> "04"
irb(main):017:0> t.strftime("%d")
=> "01"最後に
日時、時間、曜日の情報の取得に関して整理できたでしょうか?
また、万が一間違った情報がありましたら教えていただけると嬉しいです!
ご一読いただきありがとうございました!
- 投稿日:2020-04-01T18:47:45+09:00
【超初心者向け】RubyをインストールしたらRubyで遊んでみよう!!その6
※本記事はRubyがインストールされた前提の記事です。
Rubyをインストールしたあと、とにかくRubyをいろいろ触ってみて慣れていくための記事です。お役に立てば幸いです。環境
Ruby 2.5.1
MacOS Mojave Ver.10.14.6【超初心者向け】RubyをインストールしたらRubyで遊んでみよう!!その5の続きです。ここでも少し難しくなります。
Tarminalirb(main):019:0> def hi(name = "World") irb(main):020:1> puts "Hello #{name.capitalize}!" irb(main):021:1> end => :hi irb(main):022:0> hi "chris" Hello Chris! => nil irb(main):023:0> hi Hello World! => nilcapitalizeメソッド
2行目のcapitalizeメソッドでnameの先頭の小文字を大文字に変換しています。(chris▶︎Chris)
8行目のカッコなしのメソッド呼び出しではデフォルト引数のWorldが呼び出されます。 これは、「もしnameが与えられなければ、nameのデフォルト値である"World"を 使う」という定義になります。
上記のように表示されていれば成功です。お疲れ様でした。
- 投稿日:2020-04-01T17:36:33+09:00
【超初心者向け】RubyをインストールしたらRubyで遊んでみよう!!その5
※本記事はRubyがインストールされた前提の記事です。
Rubyをインストールしたあと、とにかくRubyをいろいろ触ってみて慣れていくための記事です。お役に立てば幸いです。環境
Ruby 2.5.1
MacOS Mojave Ver.10.14.6【超初心者向け】RubyをインストールしたらRubyで遊んでみよう!!その4の続きです。ここで少し難しくなります。
#{変数名}で式展開
名前を引数にhiを再定義すれば特定の人にHelloが言えます。
2行目の#{name}は文字列に何か挿入するときのRubyのやり方です(式展開)。#{変数名} と記述すると、文字列の中に変数や定数を書き込むことができます。式展開を使用するためには、文字列を必ずダブルクォート(”)で囲います。Tarminalirb(main):015:0> def hi(name) irb(main):016:1> puts "Hello #{name}!" irb(main):017:1> end => :hi irb(main):018:0> hi("Matt") Hello Matt! => nil上記のように表示されていれば成功です。お疲れ様でした。
- 投稿日:2020-04-01T17:14:21+09:00
【超初心者向け】RubyをインストールしたらRubyで遊んでみよう!!その4
※本記事はRubyがインストールされた前提の記事です。
Rubyをインストールしたあと、とにかくRubyをいろいろ触ってみて慣れていくための記事です。お役に立てば幸いです。環境
Ruby 2.5.1
MacOS Mojave Ver.10.14.6【超初心者向け】RubyをインストールしたらRubyで遊んでみよう!!その3の続きです。
何度も"Hello World"と表示させたい時は「メソッド」を定義します。Tarminalirb(main):010:0> def hi irb(main):011:1> puts "Hello World!" irb(main):012:1> end => :hidef hi という部分でメソッドを定義しています。次の行はおなじみのputs "Hello World!"ですね。そして次の行は end でRubyにメソッド定義の終わりを意味します。最後の行はRubyがメソッド定義の終わりを理解したことを表すRubyのレスポンスです。
それでは先ほどのメソッドを繰り返してみましょう。
Tarminalirb(main):013:0> hi Hello World! => nil irb(main):014:0> hi Hello World! => nil上記のように先ほど定義したhiメソッドの二文字だけを実行すれば "Hello World!" を簡単に表示させることができます。
- 投稿日:2020-04-01T16:34:37+09:00
【超初心者向け】RubyをインストールしたらRubyで遊んでみよう!!その3
※本記事はRubyがインストールされた前提の記事です。
Rubyをインストールしたあと、とにかくRubyをいろいろ触ってみて慣れていくための記事です。お役に立てば幸いです。環境
Ruby 2.5.1
MacOS Mojave Ver.10.14.6計算機としてirbと遊んでみましょう!
【超初心者向け】RubyをインストールしたらRubyで遊んでみよう!!その2の続きです。以下のようにirbを計算機のように使用してみましょう。
Tarminalirb(main):003:0> 2+2 => 4これは 2+2 の計算結果です。次に 2*3 を計算させたい場合は
Tarminalirb(main):004:0> 2*3 => 6ちなみにキーボードの上矢印(↑)を押せば前回入力した2*3を呼び出すこともできます。
次は3を2乗してみましょう。
Tarminalirb(main):005:0> 3**2 => 9上記のようになれば成功です。お疲れ様でした。
- 投稿日:2020-04-01T16:02:52+09:00
Strong Parametersでネストする場合の注意点
params = { title: "タイトル", content: { header: "見出し", body: "内容" }, author: "太郎" }みたいなパラメータをpermitしてあげるときこの順番通りに実装しようとすると
params.permit(:title, content: [:header, :body], :author)となるが、これだとsyntax errorになる
理由はネストする場合はそのネストするパラメータを最後に持ってこないといけないため。
正解はこれparams.permit(:title, :author, content: [:header, :body])
- 投稿日:2020-04-01T15:56:54+09:00
【超初心者向け】RubyをインストールしたらRubyで遊んでみよう!!その2
※本記事はRubyがインストールされた前提の記事です。
Rubyをインストールしたあと、とにかくRubyをいろいろ触ってみて慣れていくための記事です。お役に立てば幸いです。環境
Ruby 2.5.1
MacOS Mojave Ver.10.14.6【超初心者向け】RubyをインストールしたらRubyで遊んでみよう!!その1で表示させた"Hello World"はプログラムを書いたわけではありません。irbが最後の式を実行した結果を教えてくれているのです。出力したい場合は以下のように puts の後に "Hello World" と記述します。
Tarminalirb(main):002:0> puts "Hello World" Hello World => nilRubyで何かを出力基本的なコマンドとしてputsがあります。後に続く => nil はコマンドの評価結果です。 puts は必ず nil を返します。 nil は「何もない」ことを返すRubyの「値」です。
上記の通りに表示されれば成功です。お疲れ様でした。
- 投稿日:2020-04-01T15:46:08+09:00
chromebook(c101pa)にrubyをインストール
はじめに
最近rubyを使う機会が増えたため、そろそろchromebookのローカル環境でも使えるようにしたいと考え、rubyとのインストールをやってみました。
(以前から、python, gitは使用していましたが、こちらはchromebookのlinux機能をオンにすることで標準で利用できていました)実施環境
- chromebook c101pa
- Chrome OS 80.0.3987.158 (2020/4/1時点の最新版)
- linux(ベータ版) debian 10.3
やってみたこと、わかったことの概要
- rubyのインストールにはaptコマンドではなく、rbenvを使って実施。
- rbenvからrubyをインストールするには、事前にインストールが必要なパッケージがある(など)
- macや純粋なlinuxに比べてchromebookへのインストールは少し手順が複雑(文献も少ない)
手順
rbenvのインストール
rbenvをgithubからインストール
git clone https://github.com/rbenv/rbenv.git ~/.rbenvrbenvの設定を実施
# 「~/.rbenv/bin」を環境変数に追加するための記述を「~/.bashrc」に追記 $ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc # rbenvを初期化する記述を「~/.bashrc」に追記 $ echo 'eval "$(rbenv init -)"' >> ~/.bashrc # ~/.bashrcを読み込み $ source ~/.bashrc # path設定の確認 $ env | grep -i path PATH=/home/xxxxxxxx/.rbenv/shims:/home/xxxxxxxx/.rbenv/bin: ~以降略上記の手順でrbenvのインストールされたはずなので、確認します。
$ rbenv -v rbenv 1.1.2-28-gc2cfbd1ruby-buildのインストール
rbenvを実行させるために必要なプラグイン「ruby-build」をインストールします。
$ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-buildruby-buildの設定
# 「ruby-build/bin」を環境変数に追加するための記述を「~/.bashrc」に追記 $ echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bashrc完了したら、インストールできるバージョンの確認(最新の5つのバージョンのみ表示)
$ rbenv install --list | grep -v - | tail -5 2.6.4 2.6.5 2.6.6 2.7.0 2.7.1rubyのインストール
今回は2.6.xの最新バージョンである、2.6.6をインストールすることとします。が、、
$ rbenv install -v 2.6.6 /tmp/ruby-build.20200401132705.1971.uWv7bJ ~ Downloading ruby-2.6.6.tar.bz2... -> https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.6.tar.bz2 /tmp/ruby-build.20200401132705.1971.uWv7bJ/ruby-2.6.6 /tmp/ruby-build.20200401132705.1971.uWv7bJ ~ Installing ruby-2.6.6... checking for ruby... false checking build system type... aarch64-unknown-linux-gnu checking host system type... aarch64-unknown-linux-gnu checking target system type... aarch64-unknown-linux-gnu checking for gcc... no checking for cc... no checking for cl.exe... no configure: error: in `/tmp/ruby-build.20200401132705.1971.uWv7bJ/ruby-2.6.6': configure: error: no acceptable C compiler found in $PATH See `config.log' for more details BUILD FAILED (Debian GNU/Linux 10 using ruby-build 20200401-2-g504f6e6)はまったorz・・・・・
とはいえ、no acceptable C compiler found in $PATH てのが問題っぽい。cコンパイルのライブラリが足りないと推測をたて、色々ググる。
と、ここのページでは、debianのrubyインストール前にいろいろなパッケージをしていたので、これを参考に。確かにその中の build-essentialにはgccなどが含まれている。$ sudo apt install libreadline-dev zlib1g-dev libreadline-dev libncurses5-dev autoconf bison libssl-dev build-essential libyaml-dev libffi-dev libssl-dev libreadline-dev zlib1g-dev libgdbm-devaptコマンド終了後に再度実施!
$ rbenv install -v 2.6.6こんどは問題なくいってるっぽい。そして約20分後に終了。
インストールされているrubyのバージョンを確認。やったー!!$ rbenv versions 2.6.6ここまででは、単にrbenvに入っただけなので、適用にするRubyのバージョンを指定します。
今回はLinux全体に適用せるためにglobal指定で。(ディレクトリ単位などの場合はlocalを指定)# 適用させるrubyのバージョンの指定 $ rbenv global 2.6.6 # rubyのバージョン確認 $ ruby -v ruby 2.6.6p146 (2020-03-31 revision 67876) [aarch64-linux]rubyのバージョン確認も問題なし!
$ which ruby /home/XXXXX/.rbenv/shims/ruby #XXXXXはユーザ名きちんとrbenvで管理されているrubyが適用されています。
rubyの動作確認
以下のファイルを作成&実行
sample.rbputs "hello!"$ ruby sample.rb helloきちんと動いた!
参考
【rbenvの使い方】RubyをCentOSにインストールしよう【超簡単】
→ OSのディストリビューションは異なりますが(chromebookはDebian)が大変参考になりました。Github(rbenv)/Github(ruby-build)
→ いずれもreadme.mdファイルにインストールなどの説明が丁寧に書かれています。Rubyのインストール
→ パッケージマネージャ(debianの場合は"apt")を使ったインストールをおすすめしない理由が書かれています。How to Install Ruby on Debian 10
→ Debian(chromebook のlinuxデストリビューション)でrubyをインストールするマニュアル。失敗してググる中でこの記事にたどり着いたが、最初からこの記事通りにやっておけばすんなりいけたと思う。
- 投稿日:2020-04-01T15:36:03+09:00
rbenv や pyenv で困っている人のための解説
対象読者: Ruby・Pythonなどの初心者で、こんなエラーメッセージが出てきて困っている人
$ fluentd rbenv: fluentd: command not found The `fluentd' command exists in these Ruby versions: 2.6.3rbenv や pyenv などの、
**env
を使っていると、このようなエラーメッセージが出てくることがあります。この問題を解決するには
**env
の仕組みを理解する必要があります。
**env
の仕組みそもそも、
**env
を使っていると、このようにディレクトリを移動しただけで、ruby
やpython
のバージョンが自動で切り替わるようになりますが、これはどのように実現されているのでしょう?$ ruby --version ruby 2.3.7p456 (2018-03-28 revision 63024) [universal.x86_64-darwin18] $ cd my-project $ cat .ruby-version 2.4.5 $ ruby --version ruby 2.4.5p335 (2018-10-18 revision 65137) [x86_64-darwin18]実はここで実行している
ruby
は、本物のruby
ではありません!~/.rbenv/shims/
に置かれたスクリプトなのです。$ which ruby /Users/x-xxxxx/.rbenv/shims/ruby $ cat ~/.rbenv/shims/ruby #!/usr/bin/env bash set -e [ -n "$RBENV_DEBUG" ] && set -x program="${0##*/}" if [ "$program" = "ruby" ]; then for arg; do case "$arg" in -e* | -- ) break ;; */* ) if [ -f "$arg" ]; then export RBENV_DIR="${arg%/*}" break fi ;; esac done fi export RBENV_ROOT="/Users/x-xxxxx/.rbenv" exec "/Users/x-xxxxx/.rbenv/libexec/rbenv" exec "$program" "$@"
bundler
,gem
などの同梱コマンドや、Gemによって追加されるコマンドも同様です。$ which gem /Users/x-xxxxx/.rbenv/shims/gem $ which bundle /Users/x-xxxxx/.rbenv/shims/bundle $ which fluentd /Users/x-xxxxx/.rbenv/shims/fluentdそして、普通に
rbenv init
でセットアップすると、$PATHの先頭に~/.rbenv/shims/
が追加されて最優先で使用されます。エラーメッセージが表示されるまでの流れ
~/.rbenv/shims/
のスクリプトは現在使用中のバージョンのRuby内の同名のコマンドを実行しようとします。そのため、現在使用中のバージョンのRubyにコマンドがインストールされていなければ、エラーになります;
- あなたが
fluentd
を実行する$PATH
が先頭から検索され、最初に見つかる~/.rbenv/shims/fluentd
実行される~/.rbenv/shims/fluentd
は現在のディレクトリから .rbenv-version を検索し、Rubyのバージョンを決定する- そのバージョンのRuby内の
flunetd
を実行しようとするがfluentd
が見つからない(別バージョンのRubyにはインストールされているが)- 下記のエラーメッセージを表示
$ fluentd rbenv: fluentd: command not found The `fluentd' command exists in these Ruby versions: 2.6.3対策
コマンドを絶対パスで指定する
~/.rbenv/shims/hoge
を経由せずに直接実行します。~/.rbenv/versions/2.6.1/bin/ruby$PATHの先頭に追加する
~/.rbenv/shims より先にパスを追加します。
なお、RubyやPythonにはコマンドを、(インタープリターのディレクトリではなく)ホーム直下のディレクトリにインストールする機能があります。そのディレクトリを$PATHに追加すると良いでしょう。
$ export PYTHONUSERBASE=~/.local $ pip3 install --user awscli $ ls ~/.local/bin jupyter-notebook $ ls ~/.local/bin/ jupyter jupyter-bundlerextension jupyter-console jupyter-kernel ...# ~/.bashrc eval "$(pyenv init -)" export PATH=$HOME/.local/bin:$PATHrbenv init を使わない
実は
rbenv init
を使わなくても、rbenv install
でRubyをビルドすることはできます。人によってはビルドできるだけで十分かもしれません。
pipenv や bundler 経由で使う
RubyやPythonのコマンドをグローバルにインストールして使うというのが、そもそもトラブルの元です。常に pipenv や bundler 経由で使いましょう。
- 投稿日:2020-04-01T12:40:53+09:00
Action Cable の凄まじくざっくりとした概要
- 投稿日:2020-04-01T10:19:06+09:00
Rails初心者のRailsチュートリアル各章後のまとめ 1章
初投稿のため至らない点、間違っている点などあるかと思いますのでご指摘いただけると幸いです。
Rails初心者のRailsチュートリアル終了後の備忘録として投稿させていただきます。
※自分なりに色々と情報収集をして完全自己流で進めて行きます。Railsチュートリアル1章まとめ
開発環境
開発環境としてRailsチュートリアル推奨環境であるAWS Cloud9を利用しました。チュートリアルに詳細なセットアップの方法が記載してあるため、特に詰まることなくセットアップ完了。(いつかはローカル環境で開発したい。)
Railsをインストールする
Railsのインストールの前にprintf~というコマンドを入力。意味はわからなくも問題ないとのことだが一度ググってみるもほとんど情報が出てこないため置いておくことに。
早速railsをインストール。gem install rails -v(バージョンを指定)のコマンドでインストールできる。最初のアプリケーション
コンピュータープログラミングの古来からの伝統らしい「Hello World」を表示する。
その前にUNIXコマンドのまとめ
ls,mkdir,cdなどがよく使うのかな?使っていくうちに徐々に覚えていこう。
そしてやっと最初のアプリケーションrails (バージョン) new (アプリケーション名)で作成できる。
インストールしたrailsのバージョンと合わせる同じファイル構造を作成できる。
「Could not find 'railties'」はインストールしたRailsのバージョンが正しくないかもしれない。
Railsではファイル/ディレクトリ構造が標準化されていることで他の開発者が書いたコードが読みやすくなるなどのメリットがある。Bundler
Railsアプリケーションを作成したらBundlerを実行しgemをインストールする。(アプリケーション作成時は自動的に実行されている。)
Gemfileのgemを書き換えたらbundle installを実行。「bundle updateをしてください」と表示されたらそれ通りにbundle updateを実行する。
gemのバージョンの指定方法には2通りあり
「gem 'uglifier', '>= 1.3.0'」、「gem 'coffee-rails', '~> 4.0.0'」の2通りとなる。
>=という記法では常に最新のgemがインストールされ、~> 4.0.0という記法ではマイナーバージョンの部分に相当するアップデート済みのgem (例: 4.0.0から4.0.1) をインストールする。rails sever
rails severはローカルWebサーバーを起動するためのコマンド。
Model-View-Controller (MVC)
Railsアプリケーションの全体的な仕組みとしてModel,View,Controller(MVC)というアーキテクチャーモデルが採用されている。
Controller:ブラウザからのリクエストがControllerに渡される。
View:ブラウザに表示するHTMLをcontrolleに返す。
Model:Databeseと通信を担当している。Hello world!!
render:htmlを使用しhello worldを表示する。(表示したい文字列を返すアクション)
ルーターを使用しブラウザからのリクエストを振り分けておりルーティングファイルへ記載していく。(config/routes.rb)
具体的なルーティングの記載方法としてroot 'controller_name#action_name'という形式となる。Gitによるバージョン管理
開発現場においてバージョン管理システムは必要不可欠となっており開発者にとっては必須の技術となっている。
バージョン管理システムにも様々なものがあるがRailsコミュニティではGitが主流となっている。
推奨環境であるクラウドIDEではデフォルトでGitが導入されている。gitの基本的な流れ
git configで設定。 コンピューター一台につき一度の設定で良い。設定する名前、メールアドレスはリポジトリ上で一般に公開されるため注意。
git initで新しいリポジトリの初期化を行う。
git add -Aでプロジェクトのファイルをリポジトリに追加する。全てのファイルがリポジトリへ追加されるが.gitignoreに記載されているパターンのファイルは追加されない。
git statusで現在の状態を確認できる。git add後にはいきなりコミットされずステージングエリアという待機用のリポジトリに一旦置かれる。
git commitでステージングエリアにある変更を本格的にリポジトリに反映することができる。
-m""を使用することでコミットメッセージを直接指定できる。
git logでコミットメッセージの履歴を参照することができる。Github
RailsチュートリアルではBitbucketが使用されているが情報収集をしていく中で開発現場ではGithubを利用していることが多い、企業の募集要項にもGithubでのポートフォリオ公開が主流であると感じたためGithubを使用することとした。
設定方法などはチュートリアルに記載しているBitbucketの方法と大差はなく案外簡単に設定することができた。ブランチ、編集、コミット、マージ
Gtihubへpushすると自動的にREADMEファイルの内容が自動的に表示される。ファイル名が「.md」となっているファイルは自動的にHTMLとして表示してくれる。
ブランチ
ブランチは基本的にはリポジトリのコピーで元のファイルには触らずにファイルの変更などを行うことができる。
通常親リポジトリはmasterブランチと呼ばれトピックブランチは「$ git checkout -b ブランチ名」で作成することができる。「$ git branch」のコマンドで現在あるブランチの一覧を表示することができる。編集、コミット
ファイルを編集したらgit statusでブランチの状態を確認する。その後git add -A、git commitをすることもできるがgit commitにはファイルの変更を一括でコミットすることができる-aフラグが存在する。このフラグはよく使われるそう。「$ git commit -a -m ""」
※コミットメッセージは現在形かつ命令形で書くようにする。マージ
「$ git checkout master」でマスターブランチに戻り、「git merge トピックブランチ名」で変更をマスターブランチにマージできる。
「git branch -d ブランチ名」でトピックブランチを削除することができる。push
ファイルの変更が終わったら「git push」でGithubへ変更をプッシュできる。大抵のシステムでorigin masterを省略できる。
デプロイ
herokuへのデプロイはしないためデプロイに関しては割愛します。チュートリアルが終わったらAWSなどにデプロイできるよう勉強する予定。
- 投稿日:2020-04-01T08:00:16+09:00
【Ruby】Struct(構造体クラス)を理解する
Struct(構造体クラス)とは?
Rubyにおける
Struct(構造体クラス)
は簡易的なクラスのようなもので、下記のようなケースで使用されます。
- まとまったデータを扱いたいが、クラスを作るまでもない場合。
- クラス内で特定のデータのまとまりを表現する場合。
通常のクラスとの違い
主に下記のような違いがあります。
- 継承、ミックスイン、メンバの追加・削除ができない。
- 明示的にアクセスメソッドを定義しなくても、構造体クラス外でメンバの参照・更新が可能。
Struct(構造体クラス)の定義方法
定義方法(1)
第一引数には
String
で構造体クラス名
を渡します。(クラスと同じで大文字から始まる必要があります。)
第二引数以降にはSymbol
でメンバを渡します。第一引数を渡した場合
Struct.new("User", :name, :age) p Struct::User.new("Taro", 36) #=> #<struct Struct::User name="Taro", age=36>第一引数を省略した場合
下記のように、第一引数を省略して定義する事も可能です。
User = Struct.new(:name, :age) p User.new("Taro", 36) #=> #<struct User name="Taro", age=36>定義方法(2)
Struct(構造体クラス)は下記のように定義する事も可能です。
class User < Struct.new(:name, :age) end p User.new("Taro", 36) #=> #<struct User name="Taro", age=36>Struct(構造体クラス)インスタンスの作成
既に上記で確認できますが、クラスのように
new
を使用する事で、Struct(構造体クラス)
のインスタンスを作成する事が可能です。p User.new("Taro", 36) #=> #<struct User name="Taro", age=36>指定数以上の引数を渡した場合
指定された数以上の引数を与えるとエラーになります。
User.new("Taro", 36, 4) #=> struct size differs指定数に満たない数の引数を渡した場合
逆に、指定数に満たない数の引数を渡した場合は、値が渡されなかったメンバに
nil
が入り、インスタンスが作成されます。User = Struct.new(:name, :age) User.new("Taro") #=> <struct User name="Taro", age=nil>メンバの参照・更新方法
Struct(構造体クラス)
の定義時に指定したメンバは下記のように簡単に参照・更新する事が可能です。user1 = User.new("Taro", 40) # 参照 p user1.name #=> "Taro" # 更新 user1.name = "Jiro" p user1.name #=> "Jiro"Struct(構造体クラス)にメソッドを定義する
クラスのように、
do~end
のブロック内にインスタンスから呼べるメソッドを定義する事が可能です。User = Struct.new("User", :name, :age) do def intro "私の名前は#{name}" end def say(word) word end end user1 = User.new("Taro",92) p user1.intro #=> 私の名前はTaro p user1.say("こんにちは") #=> こんにちはまた、通常のクラスのように、下記のようにする事でクラスから直接呼ぶメソッドの定義をする事も可能です。
User = Struct.new("User", :name, :age) do class << self def hoge "Hoge" end end def self.huga "Huga" end end p User.hoge #=> "Hoge" p User.huga #=> "Huga"Struct(構造体クラス)インスタンスの比較
Struct(構造体クラス)
の特徴の1つにインスタンスのメンバの値が同じであれば==
で比較した際にtrue
を返す事が挙げられます。User = Struct.new(:name, :age) user1 = User.new("Taro", 45) user2 = User.new("Taro", 45) user3 = User.new("Jiro", 90) # メンバの値が同じなのでtrue p user1 == user2 #=> true # メンバの値が異なるのでfalse p user1 == user3 #=> false念の為ですが、クラスの場合は下記のようになります。
class User def initialize(name, age) @name = name @age = age end end user1 = User.new("Taro", 45) user2 = User.new("Taro", 45) # 異なるオブジェクトのためfalse p user1 == user2 #=> falseStruct(構造体クラス)の実例
最後に、使い方のイメージが付くよう
Struct(構造体クラス)
の実例を紹介します。
冒頭で挙げた通り、Struct(構造体クラス)
は下記のように、クラス内で特定のデータのまとまりを表現する場合に使われます。class User Address = Struct.new(:city, :prefecture, :country) def initialize(name, args) @name = name @address = Address.new(args[:city], args[:prefecture], args[:country]) end end user1 = User.new("Taro", { city: "Minato", prefecture: "Tokyo", country: "Japan"}) p user1 #=> <User:0x0000558567cc3408 @name="Taro", @address=#<struct User::Address city="Minato", prefecture="Tokyo", country="Japan">>参考
- 投稿日:2020-04-01T02:30:36+09:00
Library not loaded: libssl.1.1.dylib (LoadError) 発生時の対処策
状況
macOS catalina で railsアプリの開発環境を整え終わって、
Windows10環境で開発してたWebアプリをクローンし、いざrails server
実行だ!
と意気込んでいたら、見た事のないエラーに困惑…解決はできたものの、いろいろと調べたら闇が深そうだったので、
自分用にメモを残しておきます。ついでに同じエラーに悩む方の助けになれば幸いです!
エラー内容
zshdlopen(/Users/kirimaro/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.3/lib/mysql2/mysql2.bundle, 9): Library not loaded: libssl.1.1.dylib (LoadError) Referenced from: /Users/kirimaro/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.3/lib/mysql2/mysql2.bundle Reason: image not found - /Users/kirimaro/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.3/lib/mysql2/mysql2.bundle
OpenSSL@1.1
のインストールmacOS catalina はデフォルトのSSLがLibreSSL 2.8.3が設定されています。
試しにopenssl version
を実行すると、LibreSSL 2.8.3
と出力されはずです。
上記エラーの原因はこいつです。
gem "mysql2"
はOpenSSLと依存関係にあり、LibreSSLがデフォルトだとLibrary not loaded: libssl.1.1.dylib
といったロードエラーが発生します。これを解消するために、まず
OpenSSL@1.1
をインストールします。尚、
OpenSSL@1.1
はHomebrewでインストールを行いますが、
Homebrewのインストール方法については、他で調べれば記事が大量だと思うのでここでは割愛させていただきます!
※Homebrewの利用にはXcodeが必須となりますので、インストールしていない方は先にインストールしておきましょう。既にHomebrewをインストール済みの方は、以下のコマンドを実行します。
zsh% brew install openssl@1.1
インストールが完了したら、
brew info openssl
を実行します。すると
OpenSSL@1.1
について以下のような情報が表示されます。zsh% brew info openssl openssl@1.1: stable 1.1.1d (bottled) [keg-only] Cryptography and SSL/TLS Toolkit https://openssl.org/ /usr/local/Cellar/openssl@1.1/1.1.1d (7,983 files, 17.9MB) Poured from bottle on 2020-03-30 at 20:49:11 From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/openssl@1.1.rb ==> Caveats A CA file has been bootstrapped using certificates from the system keychain. To add additional certificates, place .pem files in /usr/local/etc/openssl@1.1/certs and run /usr/local/opt/openssl@1.1/bin/c_rehash openssl@1.1 is keg-only, which means it was not symlinked into /usr/local, because openssl/libressl is provided by macOS so don't link an incompatible version. If you need to have openssl@1.1 first in your PATH run: echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.zshrc For compilers to find openssl@1.1 you may need to set: export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib" export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include" For pkg-config to find openssl@1.1 you may need to set: export PKG_CONFIG_PATH="/usr/local/opt重要なのは、
echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.zshrc
で、
これをzshにコピペして実行する事で、デフォルトのSSLがOpenSSLへ変更されます。実は、エラーの解消自体はOpenSSLをインストールしていれば、デフォルトに設定していなくても問題なく解決できます。
私自身まだそこまで詳しくないのですが、開発ツールなどの多くがまだまだOpenSSLへ依存している状況もあるようなので、
デフォルトに指定しておいたほうが無難なのかなと思います。正しく切り替わったか確認したい場合は、zshを再起動したうえで
openssl version
を入力してみて下さい。
OpenSSL @1.1.1d
といった感じで、デフォルトSSLが切り替わっている事が確認できると思います。
mysql2
ビルド時に必要となるlib/includeのPathを環境変数に追加
~/.zshenv
ファイルをviなどのテキストエディタで開き、
brew info openssl
実行時に表示される下記パスを追加します。保存したら反映するために、zshを再起動します。
export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib" export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"bundle configにビルドパスを追加
railsアプリのルートディレクトリへ移動し、zshから下記コマンドを実行します。
zsh% bundle config build.mysql2 --with-ldflags=$LDFALGS --with-cppflags=$CPPFLAGSこれで、bundle installを実行した際、mysql2のインストール時にビルドオプションとして、opensslのlib/includeが読み込まれます。
Railsアプリからすでにインストール済みの
mysql2
をアンインストール下記コマンドを実行し、インストール済みのmysql2を削除します。
zsh% bundle exec gem uninstall mysql2
bundle install を実行
既に
bundle config
でビルドオプションを設定済みですので、bundle install
若しくはbundle
のみでインストール可能です。zsh% bundle install
これで、ロードエラーが解消され、正常にサーバーが起動されると思います。
デフォルトSSLをLibreSSLに戻したい時
デフォルトSSLをLibreSSLに戻したい場合は、
~/.zshrc
に追加された
export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"
を削除・保存し、zshを再起動すると、
LibreSSLがデフォルトSSLに戻ります。
- 投稿日:2020-04-01T02:30:36+09:00
Library not loaded: libssl.1.1.dylib (LoadError) 発生時の解決方法
状況
macOS catalina で railsアプリの開発環境を整え終わって、
Windows10環境で開発してたWebアプリをクローンし、いざrails server
実行だ!
と意気込んでいたら、見た事のないエラーに困惑…解決はできたものの、いろいろと調べたら闇が深そうだったので、
自分用にメモを残しておきます。ついでに同じエラーに悩む方の助けになれば幸いです!
エラー内容
zshdlopen(/Users/kirimaro/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.3/lib/mysql2/mysql2.bundle, 9): Library not loaded: libssl.1.1.dylib (LoadError) Referenced from: /Users/kirimaro/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.3/lib/mysql2/mysql2.bundle Reason: image not found - /Users/kirimaro/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.3/lib/mysql2/mysql2.bundle
OpenSSL@1.1
のインストールmacOS catalina はデフォルトのSSLがLibreSSL 2.8.3が設定されています。
試しにopenssl version
を実行すると、LibreSSL 2.8.3
と出力されはずです。
上記エラーの原因はこいつです。
gem "mysql2"
はOpenSSLと依存関係にあり、LibreSSLがデフォルトだとLibrary not loaded: libssl.1.1.dylib
といったロードエラーが発生します。これを解消するために、まず
OpenSSL@1.1
をインストールします。尚、
OpenSSL@1.1
はHomebrewでインストールを行いますが、
Homebrewのインストール方法については、他で調べれば記事が大量だと思うのでここでは割愛させていただきます!
※Homebrewの利用にはXcodeが必須となりますので、インストールしていない方は先にインストールしておきましょう。既にHomebrewをインストール済みの方は、以下のコマンドを実行します。
zsh% brew install openssl@1.1
インストールが完了したら、
brew info openssl
を実行します。すると
OpenSSL@1.1
について以下のような情報が表示されます。zsh% brew info openssl openssl@1.1: stable 1.1.1d (bottled) [keg-only] Cryptography and SSL/TLS Toolkit https://openssl.org/ /usr/local/Cellar/openssl@1.1/1.1.1d (7,983 files, 17.9MB) Poured from bottle on 2020-03-30 at 20:49:11 From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/openssl@1.1.rb ==> Caveats A CA file has been bootstrapped using certificates from the system keychain. To add additional certificates, place .pem files in /usr/local/etc/openssl@1.1/certs and run /usr/local/opt/openssl@1.1/bin/c_rehash openssl@1.1 is keg-only, which means it was not symlinked into /usr/local, because openssl/libressl is provided by macOS so don't link an incompatible version. If you need to have openssl@1.1 first in your PATH run: echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.zshrc For compilers to find openssl@1.1 you may need to set: export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib" export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include" For pkg-config to find openssl@1.1 you may need to set: export PKG_CONFIG_PATH="/usr/local/opt重要なのは、
echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.zshrc
で、
これをzshにコピペして実行する事で、デフォルトのSSLがOpenSSLへ変更されます。実は、エラーの解消自体はOpenSSLをインストールしていれば、デフォルトに設定していなくても問題なく解決できます。
私自身まだそこまで詳しくないのですが、開発ツールなどの多くがまだまだOpenSSLへ依存している状況もあるようなので、
デフォルトに指定しておいたほうが無難なのかなと思います。正しく切り替わったか確認したい場合は、zshを再起動したうえで
openssl version
を入力してみて下さい。
OpenSSL @1.1.1d
といった感じで、デフォルトSSLが切り替わっている事が確認できると思います。
mysql2
ビルド時に必要となるlib/includeのPathを環境変数に追加
~/.zshenv
ファイルをviなどのテキストエディタで開き、
brew info openssl
実行時に表示される下記パスを追加します。保存したら反映するために、zshを再起動します。
export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib" export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"bundle configにビルドパスを追加
railsアプリのルートディレクトリへ移動し、zshから下記コマンドを実行します。
※特定のアプリにのみビルドオプションを指定したい場合は、該当アプリのルートディレクトリにいる状態でローカルの方のコマンドを実行してください!zsh#グローバル(別のアプリでmysql2をインストールする場合も、ビルドオプションが適用される) % bundle config build.mysql2 --with-ldflags=$LDFALGS --with-cppflags=$CPPFLAGS #ローカル(現在ルートディレクトリとなっているアプリでのみ、mysql2インストール時にビルドオプションが渡される) % bundle config --local build.mysql2 --with-ldflags=$LDFALGS --with-cppflags=$CPPFLAGSこれで、bundle installを実行した際、mysql2のインストール時にビルドオプションとして、opensslのlib/includeが読み込まれます。
Railsアプリからすでにインストール済みの
mysql2
をアンインストール下記コマンドを実行し、インストール済みのmysql2を削除します。
zsh% bundle exec gem uninstall mysql2
bundle install を実行
既に
bundle config
でビルドオプションを設定済みですので、bundle install
若しくはbundle
のみでインストール可能です。zsh% bundle install
これで、ロードエラーが解消され、正常にサーバーが起動されると思います。
デフォルトSSLをLibreSSLに戻したい時
デフォルトSSLをLibreSSLに戻したい場合は、
~/.zshrc
に追加された
export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"
を削除・保存し、zshを再起動すると、
LibreSSLがデフォルトSSLに戻ります。
- 投稿日:2020-04-01T01:03:05+09:00
deviseに新しいカラムを追加したけどviewsに表示されない
deviseで作ったusersテーブルに新しくnameカラムを追加、新規登録画面にnameを入れるフィールドを作ったが、なぜかviewsで表示されない問題が解決したので備忘録も兼ねて記事に致します。この記事で少しでも参考になれば幸いです。
結論から申し上げますと、i18nを使うためにはi18n化されたviewsファイルでなきゃ駄目なようです
①自分はdeviseのviewsファイルを作成する際に以下コマンドで作成しました。
rails g devise:views users
②そして日本語化もしたかったのでlocaleファイルも作成
rails g devise:i18n:locale ja
①のコマンドではviewsファイルがi18n化されておらず②と噛み合っていなかったことで、nameフィールドがviewsに表示されなかったと思われます。なので①のファイルを作り直しました。
rails g devise:i18n:views users
以後、無事にviewsに表示されてめでたしめでたし。
- 投稿日:2020-04-01T00:03:18+09:00
Reactのチュートリアルの三目並べで少しRails使う #1
何度もくどいですが、前回までのReactチュートリアルの三目並べをせっかくなので勉強も兼ねてRuby Railsで作ってみようと思います。
RubyおよびRailsはProgateの無料レッスンをこなした程度です。
無理やりerbでやらせて、ボタンを押下するたびにformをsubmitしたら、超もっさりになりました・・・。
- そもそも小規模なので、クライアントで完結させた方がよい
- 使うにしてもクライアントはReactなり他のフレームワークやライブラリに任せればよい
- やり取りはajaxにして、Rails側はデータの出し入れだけやればよい
にすればよかたかも・・・。
開発環境
Rails環境:以前の記事で構築した環境を使用する
エディタ:VSCodeM(Model)V(View)C(Controller)を用意する
Controller
ターミナルで以下を実行して、コントローラーを追加します。
rails generate controller tictactoesファイルの内容はデフォルトのindexアクションがあるのみ
tictactoes_controller.rbclass TictactoesController < ApplicationController def index end endView
コントローラー作成の際に以下に
tictactoes
ディレクトリが作成されているので、そこにindex.html.erb
を作成します。
htmlの構成は前回まで使用していた
index.html
をコピーします。
RailsだとDOCTYPE
やhtml
、body
等の共通部分は自動的に作成してくれる?ようなので、必要最低限をコピーしました。index.html.erb<head> <!--<link rel="stylesheet" href="css/index.css" />--> <!--<script src="js/index.js"></script>--> </head> <body id="tictactoes-body"> <div id="root"> <div class="game"> <div class="gmae-board"> <div> <div class="board-row"> <button class="square"></button><button class="square"></button ><button class="square"></button> </div> <div class="board-row"> <button class="square"></button><button class="square"></button ><button class="square"></button> </div> <div class="board-row"> <button class="square"></button><button class="square"></button ><button class="square"></button> </div> </div> </div> <div class="game-info"> <div>次の手番: X</div> <div> <li><button>Go to game start</button></li> <!-- <li><button>Go to move #1</button></li> --> </div> </div> </div> </div> </body>
body
にid
をつけているのは、他画面のbody
のcss設定が効いてしまって背景が青がかった緑色になってしまったので、そのスタイルを上書きするためです。
↑RailsをProgate無料レッスンでやった時のhome.scss
のbody
のスタイルが効いてしまう。スタイルが全体に効いてしまう理由は以下を参考にさせていただきました。
RailsがJavaScriptやスタイルシートを読み込む仕組みCSSは
assets/stylesheees
配下に自動的に作成されるのでそこに前回のindex.css
をコピーしていきます。tictactoes.scss#tictactoes-body { font: 14px "Century Gothic", Futura, sans-serif; margin: 20px; background-color: white; ol, ul { padding-left: 30px; } .board-row:after { clear: both; content: ""; display: table; } .status { margin-bottom: 10px; } .square { background: #fff; border: 1px solid #999; float: left; font-size: 24px; font-weight: bold; line-height: 34px; height: 34px; margin-right: -1px; margin-top: -1px; padding: 0; text-align: center; width: 34px; } .square:focus { outline: none; } .kbd-navigation .square:focus { background: #ddd; } .game { display: flex; flex-direction: row; } .game-info { margin-left: 20px; } }ルーティング
routes.rb
にGETリクエストのルーティングを追加します。get 'tictactoes' => 'tictactoes#index'ここでブラウザで確認してみたいと思います。
ターミナルから
rails serverを実行した後、
http://localhost:3000/tictactoes
にアクセス。
Model
マス目のクリック状態はDBに持つことにします。
以下のテーブル構成にしました。テーブル名:squares
カラム:
カラム名 型 主キー 説明・備考 stepNumber integer O 手順番号。主キー squareNumber integer O マス目番号。主キー content string マス目の中の値(O/X) migrationファイルを作っていきます。
以下をターミナルで実行(以下はModelを作って、ついでにmigrationファイルが作られてます。)rails generate model Square stepNumber:integer:unique squareNumber:integer:unique content:string以下が作成されます。
models/square.rbclass Square < ApplicationRecord endXXXXX_create_squares.rbclass CreateSquares < ActiveRecord::Migration[6.0] def change create_table :squares do |t| t.integer :stepNumber t.integer :squareNumber t.string :content t.timestamps end end endあれ?できたmigrationファイルに主キーの指定がない・・・
なんでや・・・理由はわかりませんが、手動で以下を追加を追加すればよいと思います。
※null不可も追加しました。
主キーに指定しただけでは勝手にnull不可とはならないようです。
後で他のSQLクライアントでスキーマ情報を見てみるとわかりますが、id
というカラムが主キー、null不可で自動的に作成されています。
そのカラムがあるからでしょうか・・・。add_index :squares, [:stepNumber, :squareNumber], :unique => true change_column :squares, :stepNumber, :integer, null: false change_column :squares, :squareNumber, :integer, null: false私は一度テーブルを作ってしまって後から追加しました。
ファイルが用意できたら、マイグレーションファイルを実行
rails db:migrateフリーのSQLクライアント
A5:SQL Mk-2
を使用してテーブルを確認してみます。インストール後、起動
「データベース」を右クリック → 「データベースの追加と削除」
左下の「追加」
「SQLite(sqlite3.dll経由)」を選択
db/development.sqlite3
を選択します。
追加すると、左のツリーに合わられるので、展開してテーブル情報を確認します。
ログイン情報を求められたら、空欄のままOKで良いです。データタブを開き、マス目に未入力の状態のレコードを作成します。
Excel等で作って、コピー&ペーストすればデータが作成されます。
マス目に数字を表示する
squareテーブルの各レコード.contentに数値を入れておいて、その値を表示させたいと思います。
全件取得します。
tictactoes_controller.rbclass TictactoesController < ApplicationController def index @Squares = Square.all end enderbの方ではマス目が3つごとに
<div class="board-row">
に囲われる必要があるので、if文で制御してます。index.html.erb<head> <!--<link rel="stylesheet" href="css/index.css" />--> <!--<script src="js/index.js"></script>--> </head> <body id="tictactoes-body"> <div id="root"> <div class="game"> <div class="gmae-board"> <div> <% @Squares.each.with_index do |sq, i| %> <% if i % 3 == 0%> <div class="board-row"></div> <% end %> <button class="square"> <%= sq.content %> </button> <% end %> </div> </div> <div class="game-info"> <div>次の手番: X</div> <div> <li><button>Go to game start</button></li> <!-- <li><button>Go to move #1</button></li> --> </div> </div> </div> </div> </body>X/Oを入力できるようにする
全然Railsわからなくてめちゃ苦戦しました。
突っ込みどころ満載かもですがtictactoes.js(function() { window.addEventListener("DOMContentLoaded", () => { // 全square document.querySelectorAll(".square").forEach((element, index) => { // クリックイベント element.addEventListener("click", squareClick.bind(element, index)); }); let isClick = false; function squareClick(index) { // 2度押し防止、既に値が埋まっているか if (isClick || this.value) { return; } else { isClick = true; } document.getElementById("clickNo").value = index; document.forms[0].submit(); } }); })();formのsubmitなのでもっさりしており、クリック連打で重複してリクエストしていたので、防止しました。
また、学習の為、ほとんどサーバー側でやってみよう!と思っていたのですが私には制御が難しくて、「既に埋まっていたら」という判定はクライアントでやっています。自作jsを何処に置けばよいかググっていたところ、
assets/javascripts
らしいのだが、これはRails5までであり、
Rails6では、assets/javascripts
フォルダが無くなっており、jsを何処に置けばいいのかわからない。→
app/javascript/packs
に配置して、読み込ませたいhtmlのヘッダで以下のように指定すると読み込めた。<%= javascript_pack_tag 'tictactoes', 'data-turbolinks-track': 'reload' %>でもこれだと、複数のjsを読み込むことになり、1つに圧縮してリクエストを減らしていた恩恵が受けられなくなる・・・。
ドウシヨウ
erbの方はform_withというものを使ってみます。
index.html.erb<head> <!--<link rel="stylesheet" href="css/index.css" />--> <!--<script src="js/index.js"></script>--> <%= javascript_pack_tag 'tictactoes', 'data-turbolinks-track': 'reload' %> </head> <body id="tictactoes-body"> <%= form_with(url: "/tictactoes/sqClick", method: "post") do |f| %> <div id="root"> <div class="game"> <div class="gmae-board"> <div> <% @Squares.each.with_index do |sq, i| %> <% if i % 3 == 0%> <div class="board-row"></div> <% end %> <%= f.text_field '', :name => "item[]", :class => "square", :readonly => true , :value => sq.content%> <% end %> </div> </div> <div class="game-info"> <div><%= @nextStepMessage %></div> <div> <li><button>Go to game start</button></li> <!-- <li><button>Go to move #1</button></li> --> </div> </div> </div> </div> <%= f.hidden_field :clickNo, :value => @clickNo %> <%= f.hidden_field :xIsNext, :value => @xIsNext %> <%= f.hidden_field :stepNumber, :value => @stepNumber %> <% end %> </body>うーん。独特すぎて謎汗
サーバー側
tictactoes_controller.rbclass TictactoesController < ApplicationController #初期画面表示 def index # 手順1以降を削除して初期化 Square.where.not(stepNumber: 0).delete_all #手順0を全件取得 @Squares = Square.all #初期はX @xIsNext = "true" #手順の開始は0から @stepNumber = 0 #次の手番の文字列 @nextStepMessage = "次の手番: X" end #マス目クリック def sqClick #現在の手順インクリメント stepNumber = params[:stepNumber].to_i @stepNumber = stepNumber.succ #マス目の配列 items = params[:item] sq = Square.new #既に勝敗が着いた if sq.calculateWinner(items) return else #XとOを設定 items[params[:clickNo].to_i] = params[:xIsNext] == "true" ? "X": "O" isWinner = sq.calculateWinner(items) #新しい手順をDBに保存 sq.saveSquare(@stepNumber, items) #フラグの更新 @xIsNext = params[:xIsNext] == "true" ? "false" : "true" #次の手番の文字列更新 if isWinner @nextStepMessage = "勝者:" + (params[:xIsNext] == "true" ? "X": "O") else @nextStepMessage = params[:xIsNext] == "true" ? "次の手番: O": "次の手番: X" end #最新の手順を取り直す @Squares = Square.where(stepNumber: @stepNumber) end #indexのhtmlを使いまわす render action: :index end end自分でも何をやっているのやら・・・。
@変数 でview側に値を埋め込めます。
render action: :index
でindex.html.erb
を使いまわしています。
判定処理calculateWinner
やレコード追加処理はモデルSquare
でやっています。Square.rbclass Square < ApplicationRecord Lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6] ] def saveSquare(stepNumber, items) #新しい手順のマス目保存処理 for i in 1..9 do Square.create(stepNumber: stepNumber, squareNumber: i, content: items[i - 1]) end end #勝敗判定関数(公式チュートリアルから拝借) def calculateWinner(items) for l in Lines do a, b, c = l if items[a] != "" && items[a] == items[b] && items[a] == items[c] return true end end return false end endテーブルデータ
id stepNumber squareNumber content created_at updated_at 1 0 1 2020/03/29 1899/12/30 1:50:48 2 0 2 2020/03/29 1899/12/30 1:51:11 3 0 3 2020/03/29 1899/12/30 1:51:11 4 0 4 2020/03/29 1899/12/30 1:50:48 5 0 5 2020/03/29 1899/12/30 1:51:11 6 0 6 2020/03/29 1899/12/30 1:51:11 7 0 7 2020/03/29 1899/12/30 1:50:48 8 0 8 2020/03/29 1899/12/30 1:51:11 9 0 9 2020/03/29 1899/12/30 1:51:11 10 1 1 2020/03/31 14:50:05 2020/03/31 14:50:05 11 1 2 2020/03/31 14:50:05 2020/03/31 14:50:05 12 1 3 X 2020/03/31 14:50:05 2020/03/31 14:50:05 13 1 4 2020/03/31 14:50:05 2020/03/31 14:50:05 14 1 5 2020/03/31 14:50:05 2020/03/31 14:50:05 15 1 6 2020/03/31 14:50:05 2020/03/31 14:50:05 16 1 7 2020/03/31 14:50:05 2020/03/31 14:50:05 17 1 8 2020/03/31 14:50:05 2020/03/31 14:50:05 18 1 9 2020/03/31 14:50:05 2020/03/31 14:50:05 19 2 1 2020/03/31 14:50:06 2020/03/31 14:50:06 20 2 2 2020/03/31 14:50:06 2020/03/31 14:50:06 21 2 3 X 2020/03/31 14:50:06 2020/03/31 14:50:06 22 2 4 2020/03/31 14:50:06 2020/03/31 14:50:06 23 2 5 2020/03/31 14:50:06 2020/03/31 14:50:06 24 2 6 O 2020/03/31 14:50:06 2020/03/31 14:50:06 25 2 7 2020/03/31 14:50:06 2020/03/31 14:50:06 26 2 8 2020/03/31 14:50:06 2020/03/31 14:50:06 27 2 9 2020/03/31 14:50:06 2020/03/31 14:50:06 28 3 1 2020/03/31 14:50:07 2020/03/31 14:50:07 29 3 2 2020/03/31 14:50:07 2020/03/31 14:50:07 30 3 3 X 2020/03/31 14:50:07 2020/03/31 14:50:07 31 3 4 2020/03/31 14:50:07 2020/03/31 14:50:07 32 3 5 2020/03/31 14:50:07 2020/03/31 14:50:07 33 3 6 O 2020/03/31 14:50:07 2020/03/31 14:50:07 34 3 7 2020/03/31 14:50:07 2020/03/31 14:50:07 35 3 8 2020/03/31 14:50:07 2020/03/31 14:50:07 36 3 9 X 2020/03/31 14:50:07 2020/03/31 14:50:07 37 4 1 2020/03/31 14:50:10 2020/03/31 14:50:10 38 4 2 2020/03/31 14:50:10 2020/03/31 14:50:10 39 4 3 X 2020/03/31 14:50:10 2020/03/31 14:50:10 40 4 4 2020/03/31 14:50:10 2020/03/31 14:50:10 41 4 5 2020/03/31 14:50:10 2020/03/31 14:50:10 42 4 6 O 2020/03/31 14:50:10 2020/03/31 14:50:10 43 4 7 2020/03/31 14:50:10 2020/03/31 14:50:10 44 4 8 O 2020/03/31 14:50:10 2020/03/31 14:50:10 45 4 9 X 2020/03/31 14:50:10 2020/03/31 14:50:10 46 5 1 2020/03/31 14:50:11 2020/03/31 14:50:11 47 5 2 2020/03/31 14:50:11 2020/03/31 14:50:11 48 5 3 X 2020/03/31 14:50:11 2020/03/31 14:50:11 49 5 4 2020/03/31 14:50:11 2020/03/31 14:50:11 50 5 5 X 2020/03/31 14:50:11 2020/03/31 14:50:11 51 5 6 O 2020/03/31 14:50:11 2020/03/31 14:50:11 52 5 7 2020/03/31 14:50:11 2020/03/31 14:50:11 53 5 8 O 2020/03/31 14:50:11 2020/03/31 14:50:11 54 5 9 X 2020/03/31 14:50:11 2020/03/31 14:50:11 55 6 1 2020/03/31 14:50:12 2020/03/31 14:50:12 56 6 2 2020/03/31 14:50:12 2020/03/31 14:50:12 57 6 3 X 2020/03/31 14:50:12 2020/03/31 14:50:12 58 6 4 2020/03/31 14:50:12 2020/03/31 14:50:12 59 6 5 X 2020/03/31 14:50:12 2020/03/31 14:50:12 60 6 6 O 2020/03/31 14:50:12 2020/03/31 14:50:12 61 6 7 O 2020/03/31 14:50:12 2020/03/31 14:50:12 62 6 8 O 2020/03/31 14:50:12 2020/03/31 14:50:12 63 6 9 X 2020/03/31 14:50:13 2020/03/31 14:50:13 64 7 1 X 2020/03/31 14:50:14 2020/03/31 14:50:14 65 7 2 2020/03/31 14:50:14 2020/03/31 14:50:14 66 7 3 X 2020/03/31 14:50:14 2020/03/31 14:50:14 67 7 4 2020/03/31 14:50:14 2020/03/31 14:50:14 68 7 5 X 2020/03/31 14:50:14 2020/03/31 14:50:14 69 7 6 O 2020/03/31 14:50:14 2020/03/31 14:50:14 70 7 7 O 2020/03/31 14:50:14 2020/03/31 14:50:14 71 7 8 O 2020/03/31 14:50:14 2020/03/31 14:50:14 72 7 9 X 2020/03/31 14:50:14 2020/03/31 14:50:14 感想
Railsの書き方が独特すぎて超苦戦してます。
今のところメリット感じない・・・次回は履歴機能を持たせて完成に持っていこうと思います。
いつかRailsのチュートリアルもやってもっと基礎から理解しよう・・・。
- 投稿日:2020-04-01T00:00:16+09:00
docker上でRailsアプリを動かす際に、localhostでアクセスするためのオプション
はじめに
学習環境を用意するにおいて、コンテナ上に開発環境が用意できることで、
・ホストPCを汚すことなく環境を構築できる
・異なるPC間で同じ環境を使いまわせる
・格好いいと感じるといった理由からRailsアプリをコンテナ上で作って動かしてみましたが、ホストからの動作確認で引っかかったのでメモします。
※動かしたのはscaffoldレベルのアプリです先にまとめ
先に問題と解決をまとめて書いておきます
- 問題
- コンテナ内で起動したRailsアプリにホストからアクセスできない
- 解決方法
- Railsアプリを立ち上げる際に、
-b 0.0.0.0
をオプションとしてつける以下、やったこと
まとめにたどり着くまでの状況をつらつらと
準備
ポートフォワードの設定をしてコンテナを立ち上げる
(ホスト)$ docker container run -it -p 30000:3000 /bin/bashコンテナ内でアプリを作る
(コンテナ)# rails new scaffold_sample (コンテナ)# bin/rails db:createデータベースを作成無事できたことを確認した後にアプリケーションを起動
(コンテナ)# bin/rails server上記手順で起動したアプリにアクセスできない
ホストからlocalhost:30000(30000はポートフォワードの設定)にアクセスしてもアプリケーションからの応答はなし
ホストのターミナルからcurlを実行してもエラー
コンテナからlocalhost宛にcurlを実行すると正常な(期待する)htmlが返却される解決手段
アプリケーションの起動時に
-b 0.0.0.0
をオプションとしてつける(コンテナ)# bin/rails server -b 0.0.0.0するとホスト側のブラウザからアプリケーションにアクセスできるようになった
「localhost:30000」「127.0.0.1:30000」「0.0.0.0:30000」のどれでもlocalhostにアクセスできていたオプションの値について
上記の通り、
-b 0.0.0.0
を指定することでによってホストからのアクセスは可能になったが
-b 127.0.0.1
や-b localhost
ではホストからアクセスできるようにはならなかった個人的なメモとして
0.0.0.0や127.0.0.1の持つ意味や、コンテナからみた自分のIP、コンテナからみたホストのIP、ホストから見たコンテナのIPなどが、
ネットワークのオプションとともにどのように振る舞うのかを学ばなければならないと感じた