20200401のRubyに関する記事は27件です。

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 update
sudo apt-get -y install git curl g++ make zlib1g-dev libssl-dev libreadline-dev libyaml-dev libxml2-dev libxslt-dev sqlite3 libsqlite3-dev nodejs

ホームディレクトリに移動します

cd
git clone git://github.com/sstephenson/rbenv.git .rbenv

パスを通します

echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
exec $SHELL
mkdir -p ~/.rbenv/plugins

作成したディレクトリに移動

cd ~/.rbenv/plugins

git上のファイルをクローンします

git clone git://github.com/sstephenson/ruby-build.git

rubyのバージョン(今回は2.6.3)を指定してinstallします

rbenv install 2.6.3

使用するrubyのバージョンをコンピュータに知らせます

rbenv global 2.6.3

rubyが正常にインストールされているか確認します(バージョンが表示されます)

ruby -v

Ruby 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.list
sudo apt-get update && sudo apt-get install yarn

完了しました。
もう一度webpackerをインストール

rails webpacker:install

完了したらもう一度'rails s'でサーバーを起動し、ブラウザ(chromeやexplolerなど)でいかにアクセスします
(わからない人はコピーして検索欄に貼り付けましょう)

localhost:3000

これで以下のような画面がでたら完了です
(わーい)

Screenshot from 2020-04-01 23-22-12.png

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

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/

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

【Ruby】カードゲームZENOをターミナル上で、コンピューターと対戦

カードゲームZENOがターミナル上で、コンピューターと対戦できるアプリを作りました。

制作期間は5日間です。

ZENOをご存じない方の為に説明しますと、オリエンタルラジオの中田敦彦さんがプロデュースした新しいカードゲームです。UNOの売り上げをこえたことや、YOUTUBE上でメンタリストのDAIGOさんと白熱した心理戦を繰り広げられたことで話題になりました。

もともとはラブレターというカードゲームを題材として作られており、そこに中田敦彦さんが新たに手を加えて二ヶ月ほどで世界観や設定を作り上げました。AMAZONで770円ほどで購入することができます。

あまり興味がない方もいるかと思いますが、中田敦彦さんはDAIGOさん以外にも、ホリエモン、YOUTUBEのヒカルさん、クイズ王の伊沢拓司とも白熱した戦いをYOUTUBE上で公開してますのでそちらだけでもぜひ見てみてください。

自分が作ったアプリの動作ですが、最終ターンまでにいけたので恐らく不備はないかと思います。

ないと信じたい。

現在就活中ですので、今後WEB上でプレイできるようにしたり、コードもリファクタリングする予定です。

引数の数がえげつくなってたり、コードが冗長すぎるなどありますが、今後改善します。

とりあえず、友人への公開用にコードを公開してあります。
とりあえず動けばいいと思って作ったので、コードはあまり綺麗ではありません。

zeno.rb
require './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.rb
def 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.rb
def 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.rb
def 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.rb
def 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

以上、また改善します。

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

【Ruby】カードゲームZENOをコンピューターと対戦して遊べるアプリ

カードゲームZENOをターミナル上で、コンピューターと対戦して遊べるアプリを作りました。

制作期間は5日間です。

ZENOをご存じないかたのために説明しますと、オリエンタルラジオの中田敦彦さんがプロデュースした新しいカードゲームです。UNOの売り上げをこえたことや、YOUTUBE上でメンタリストのDAIGOさんと白熱した心理戦を繰り広げられたことで話題になりました。

もともとはラブレターというカードゲームを題材として作られており、そこに中田敦彦さんが新たに手を加えて二ヶ月ほどで世界観や設定を作り上げました。AMAZONで770円ほどで購入することができます。

あまり興味がない方もいるかと思いますが、中田敦彦さんはDAIGOさん以外にも、ホリエモン、YOUTUBEのヒカルさん、クイズ王の伊沢拓司とも白熱した戦いをYOUTUBE上で公開してますので、そちらだけでもぜひ見てみてください。

現在就活中ですので、今後WEB上でプレイできるようにしたり、コードもリファクタリングする予定です。

友人への閲覧用にコードを公開してあります。

zeno.rb
require './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.rb
def 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.rb
def 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.rb
def 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.rb
def 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

以上、また改善します。

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

ログイン機能つけてくよ。〜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
  end
user = 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
end

include...モジュールを呼ぶためのメソッド

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
end

Sessionを破棄する

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
end

log_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できる。

今日はここまで

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

ほぼ未経験の僕、アプリ開発するってよ。[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
ドン!!!

どうでしょうか?見ていただけましたかね。
もちろんまだプロトタイプなので改善点は多いのですが、それを気にしてたらいつまでも公開できないなと思ったので公開しちゃいます。
少しでも触って頂けたら泣いて喜びます:joy:

ここに注目!

プログラムを用いて簡単な解説をしようと思ったのですが、思ったよりもヴォリュームがあったので別記事におこします!

現在準備中ですm(. .)m

まとめ

再三言いますが、やはりエンジニアは学習することが多いですね。
エンジニアが触れる領域は、広くて深いです。
様々技術を活用して一つのサービスが形になっているというのがよくわかりました。
学習をだらだらやっていると、いつまで経っても一人前のエンジニアにはなれないと危機感を感じると共に、案外エンジニアは楽じゃないなと感じました。
と言いますのも、私は皆さんがよく知るようなインフルエンサーに煽られてプログラミングを始めた経緯があります。完全にエンジニアをなめてました笑

同じくエンジニアを目指されている方に少しでも参考になれば幸いです!

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

Kinx ライブラリ - Signal

Zip

はじめに

「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。言語はライブラリが命。ということでライブラリの使い方編。

今回は Zip です。

アプリ作る時に 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。その他、指定できるのは storebzip2lzma
  • 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 を指定し、同名ファイルが既に存在した場合、スキップする。

尚、overwriteskip も指定されずに同名ファイルが存在した場合、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 ファイル作りたい。

では、また次回。

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

Kinx ライブラリ - Zip

Zip

はじめに

「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。言語はライブラリが命。ということでライブラリの使い方編。

今回は Zip です。

アプリ作る時に 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 を指定し、同名ファイルが既に存在した場合、スキップする。

尚、overwriteskip も指定されずに同名ファイルが存在した場合、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 ファイル作りたい。

では、また次回。

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

フィボナッチ数列 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) # => 89
rubyarray.rb
N = 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] # => 89

javascript の様にハッシュではなく配列でも実装可能と思われます。

Perl

perlmemo.pl
my %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"; # => 89
perlarray.pl
my $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"; # => 89
ruby_perl.pl
  return @memo[n] if @memo[n]            # ruby

  return $memo{$n} if defined $memo{$n}; # perl
  return $memo{$n} if exists $memo{$n};  # perl

ruby と異なり、defined関数もしくは exists関数を使用する必要があります。

JavaScript

javascriptmemo.js
var 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)); // => 89
javascriptarray.js
N = 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]); // => 89

javascript に詳しくないので、ハッシュ未使用で実装しています。

まとめ

  • フィボナッチ数列を実装した
  • それぞれの言語に違いがあって面白かった

参照したサイト
フィボナッチ数列
メモ化から学ぶ早期リターン
javascriptのハッシュライブラリを比較する
exists関数 - ハッシュのキーの存在確認

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

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.rb
class 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.rb
class 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

end
1ページ目で入力した情報のバリデーションチェック
  • 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する。

難しい!でも自作アプリには入れたいです!もうひと頑張り!

続きは次回!

さいごに

日々勉強中ですので、随時更新します。
皆様の復習にご活用頂けますと幸いです。

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

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リファレンスにある表を眺めて概要を理解しましょう!
スクリーンショット 2020-04-01 18.54.09.png
この表からいくつか抜粋して、実際の開発や業務で役に立ちそうなものを、コードを用いて紹介していきます!

できること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"

最後に

日時、時間、曜日の情報の取得に関して整理できたでしょうか?
また、万が一間違った情報がありましたら教えていただけると嬉しいです!
ご一読いただきありがとうございました!

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

【超初心者向け】RubyをインストールしたらRubyで遊んでみよう!!その6

※本記事はRubyがインストールされた前提の記事です。
Rubyをインストールしたあと、とにかくRubyをいろいろ触ってみて慣れていくための記事です。お役に立てば幸いです。

環境

Ruby 2.5.1
MacOS Mojave Ver.10.14.6

【超初心者向け】RubyをインストールしたらRubyで遊んでみよう!!その5の続きです。ここでも少し難しくなります。

Tarminal
irb(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!
=> nil

capitalizeメソッド

2行目のcapitalizeメソッドでnameの先頭の小文字を大文字に変換しています。(chris▶︎Chris)

8行目のカッコなしのメソッド呼び出しではデフォルト引数のWorldが呼び出されます。 これは、「もしnameが与えられなければ、nameのデフォルト値である"World"を 使う」という定義になります。

上記のように表示されていれば成功です。お疲れ様でした。

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

【超初心者向け】RubyをインストールしたらRubyで遊んでみよう!!その5

※本記事はRubyがインストールされた前提の記事です。
Rubyをインストールしたあと、とにかくRubyをいろいろ触ってみて慣れていくための記事です。お役に立てば幸いです。

環境

Ruby 2.5.1
MacOS Mojave Ver.10.14.6

【超初心者向け】RubyをインストールしたらRubyで遊んでみよう!!その4の続きです。ここで少し難しくなります。

#{変数名}で式展開

名前を引数にhiを再定義すれば特定の人にHelloが言えます。
2行目の#{name}は文字列に何か挿入するときのRubyのやり方です(式展開)。#{変数名} と記述すると、文字列の中に変数や定数を書き込むことができます。式展開を使用するためには、文字列を必ずダブルクォート(”)で囲います。

Tarminal
irb(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

上記のように表示されていれば成功です。お疲れ様でした。

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

【超初心者向け】RubyをインストールしたらRubyで遊んでみよう!!その4

※本記事はRubyがインストールされた前提の記事です。
Rubyをインストールしたあと、とにかくRubyをいろいろ触ってみて慣れていくための記事です。お役に立てば幸いです。

環境

Ruby 2.5.1
MacOS Mojave Ver.10.14.6

【超初心者向け】RubyをインストールしたらRubyで遊んでみよう!!その3の続きです。
何度も"Hello World"と表示させたい時は「メソッド」を定義します。

Tarminal
irb(main):010:0> def hi
irb(main):011:1> puts "Hello World!"
irb(main):012:1> end
=> :hi

def hi という部分でメソッドを定義しています。次の行はおなじみのputs "Hello World!"ですね。そして次の行は end でRubyにメソッド定義の終わりを意味します。最後の行はRubyがメソッド定義の終わりを理解したことを表すRubyのレスポンスです。

それでは先ほどのメソッドを繰り返してみましょう。

Tarminal
irb(main):013:0> hi
Hello World!
=> nil
irb(main):014:0> hi
Hello World!
=> nil

上記のように先ほど定義したhiメソッドの二文字だけを実行すれば "Hello World!" を簡単に表示させることができます。

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

【超初心者向け】RubyをインストールしたらRubyで遊んでみよう!!その3

※本記事はRubyがインストールされた前提の記事です。
Rubyをインストールしたあと、とにかくRubyをいろいろ触ってみて慣れていくための記事です。お役に立てば幸いです。

環境

Ruby 2.5.1
MacOS Mojave Ver.10.14.6

計算機としてirbと遊んでみましょう!

【超初心者向け】RubyをインストールしたらRubyで遊んでみよう!!その2の続きです。以下のようにirbを計算機のように使用してみましょう。

Tarminal
irb(main):003:0> 2+2
=> 4

これは 2+2 の計算結果です。次に 2*3 を計算させたい場合は

Tarminal
irb(main):004:0> 2*3
=> 6

ちなみにキーボードの上矢印(↑)を押せば前回入力した2*3を呼び出すこともできます。

次は3を2乗してみましょう。

Tarminal
irb(main):005:0> 3**2
=> 9

上記のようになれば成功です。お疲れ様でした。

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

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])
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【超初心者向け】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" と記述します。

Tarminal
irb(main):002:0> puts "Hello World"
Hello World
=> nil

Rubyで何かを出力基本的なコマンドとしてputsがあります。後に続く => nil はコマンドの評価結果です。 puts は必ず nil を返します。 nil は「何もない」ことを返すRubyの「値」です。

上記の通りに表示されれば成功です。お疲れ様でした。

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

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 ~/.rbenv

rbenvの設定を実施

# 「~/.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-gc2cfbd1

ruby-buildのインストール

rbenvを実行させるために必要なプラグイン「ruby-build」をインストールします。

$ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build

ruby-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.1

rubyのインストール

今回は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-dev

aptコマンド終了後に再度実施!

$ 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.rb
puts "hello!"
$ ruby sample.rb
hello

きちんと動いた!

参考

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

rbenv や pyenv で困っている人のための解説

対象読者: Ruby・Pythonなどの初心者で、こんなエラーメッセージが出てきて困っている人

$ fluentd
rbenv: fluentd: command not found

The `fluentd' command exists in these Ruby versions:
   2.6.3

rbenv や pyenv などの、**env を使っていると、このようなエラーメッセージが出てくることがあります。

この問題を解決するには**envの仕組みを理解する必要があります。

**env の仕組み

そもそも、**envを使っていると、このようにディレクトリを移動しただけで、rubypythonのバージョンが自動で切り替わるようになりますが、これはどのように実現されているのでしょう?

$ 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にコマンドがインストールされていなければ、エラーになります;

  1. あなたが fluentdを実行する
  2. $PATHが先頭から検索され、最初に見つかる~/.rbenv/shims/fluentd実行される
  3. ~/.rbenv/shims/fluentdは現在のディレクトリから .rbenv-version を検索し、Rubyのバージョンを決定する
  4. そのバージョンのRuby内のflunetdを実行しようとするがfluentdが見つからない(別バージョンのRubyにはインストールされているが)
  5. 下記のエラーメッセージを表示
$ 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:$PATH

rbenv init を使わない

実は rbenv init を使わなくても、rbenv install でRubyをビルドすることはできます。

人によってはビルドできるだけで十分かもしれません。

pipenv や bundler 経由で使う

RubyやPythonのコマンドをグローバルにインストールして使うというのが、そもそもトラブルの元です。常に pipenv や bundler 経由で使いましょう。

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

Action Cable の凄まじくざっくりとした概要

Action Cableとは

websocketを簡単に実装できちゃうすごいやつなのだ

ざっくりどう使うのか

・ assets/javascripts/channelsディレクトリ xxx.coffee
・ channelsディレクトリ XXX_channel.rb
上記の2つで情報のキャッチボールをさせるイメージ

続きは追々書いていきます

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

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などにデプロイできるよう勉強する予定。

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

【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 #=> false

Struct(構造体クラス)の実例

最後に、使い方のイメージが付くよう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">>

参考

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

Library not loaded: libssl.1.1.dylib (LoadError) 発生時の対処策

状況

macOS catalina で railsアプリの開発環境を整え終わって、
Windows10環境で開発してたWebアプリをクローンし、いざrails server実行だ!
と意気込んでいたら、見た事のないエラーに困惑…

解決はできたものの、いろいろと調べたら闇が深そうだったので、
自分用にメモを残しておきます。

ついでに同じエラーに悩む方の助けになれば幸いです!

エラー内容

zsh
dlopen(/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.1Homebrewでインストールを行いますが、
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に戻ります。

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

Library not loaded: libssl.1.1.dylib (LoadError) 発生時の解決方法

状況

macOS catalina で railsアプリの開発環境を整え終わって、
Windows10環境で開発してたWebアプリをクローンし、いざrails server実行だ!
と意気込んでいたら、見た事のないエラーに困惑…

解決はできたものの、いろいろと調べたら闇が深そうだったので、
自分用にメモを残しておきます。

ついでに同じエラーに悩む方の助けになれば幸いです!

エラー内容

zsh
dlopen(/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.1Homebrewでインストールを行いますが、
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に戻ります。

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

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に表示されてめでたしめでたし。

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

Reactのチュートリアルの三目並べで少しRails使う #1

何度もくどいですが、前回までのReactチュートリアルの三目並べをせっかくなので勉強も兼ねてRuby Railsで作ってみようと思います。

RubyおよびRailsはProgateの無料レッスンをこなした程度です。

無理やりerbでやらせて、ボタンを押下するたびにformをsubmitしたら、超もっさりになりました・・・。

  • そもそも小規模なので、クライアントで完結させた方がよい
  • 使うにしてもクライアントはReactなり他のフレームワークやライブラリに任せればよい
  • やり取りはajaxにして、Rails側はデータの出し入れだけやればよい

にすればよかたかも・・・。

開発環境

Rails環境:以前の記事で構築した環境を使用する
エディタ:VSCode

M(Model)V(View)C(Controller)を用意する

Controller

ターミナルで以下を実行して、コントローラーを追加します。

rails generate controller tictactoes

image.png

ファイルの内容はデフォルトのindexアクションがあるのみ

tictactoes_controller.rb
class TictactoesController < ApplicationController
    def index
    end
end

View

コントローラー作成の際に以下にtictactoesディレクトリが作成されているので、そこにindex.html.erbを作成します。
image.png

htmlの構成は前回まで使用していたindex.htmlをコピーします。
RailsだとDOCTYPEhtmlbody等の共通部分は自動的に作成してくれる?ようなので、必要最低限をコピーしました。

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>

bodyidをつけているのは、他画面のbodyのcss設定が効いてしまって背景が青がかった緑色になってしまったので、そのスタイルを上書きするためです。
image.png
RailsをProgate無料レッスンでやった時home.scssbodyのスタイルが効いてしまう。

スタイルが全体に効いてしまう理由は以下を参考にさせていただきました。
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にアクセス。
image.png

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.rb
class Square < ApplicationRecord
end
XXXXX_create_squares.rb
class 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
を使用してテーブルを確認してみます。

インストール後、起動
image.png
「データベース」を右クリック → 「データベースの追加と削除」
image.png
左下の「追加」
image.png
「SQLite(sqlite3.dll経由)」を選択
image.png
db/development.sqlite3を選択します。
image.png
追加すると、左のツリーに合わられるので、展開してテーブル情報を確認します。
image.png
ログイン情報を求められたら、空欄のままOKで良いです。

squaresのスキーマの確認結果
image.png

データタブを開き、マス目に未入力の状態のレコードを作成します。
Excel等で作って、コピー&ペーストすればデータが作成されます。
image.png

image.png

マス目に数字を表示する

squareテーブルの各レコード.contentに数値を入れておいて、その値を表示させたいと思います。

squareテーブル
image.png

全件取得します。

tictactoes_controller.rb
class TictactoesController < ApplicationController
    def index
        @Squares = Square.all
    end
end

erbの方ではマス目が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>

実行結果
image.png

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つに圧縮してリクエストを減らしていた恩恵が受けられなくなる・・・。
image.png

ドウシヨウ

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.rb
class 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: :indexindex.html.erbを使いまわしています。
判定処理calculateWinnerやレコード追加処理はモデルSquareでやっています。

Square.rb
class 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

image.png

テーブルデータ

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のチュートリアルもやってもっと基礎から理解しよう・・・。

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

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などが、
ネットワークのオプションとともにどのように振る舞うのかを学ばなければならないと感じた

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