20200515のRubyに関する記事は22件です。

ruby:2.5.0-alpine3.7 に gem install sqlite3 する

ruby:2.5.0-alpine3.7 の Docker image に sqlite3 をインストールしようとしたときのメモです。
このときは、sqlite3 の 1.4.2 が最新でした。

make, g++, sqlite-dev を入れると install できました

/ # apk update
fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/community/x86_64/APKINDEX.tar.gz
v3.7.3-180-g5372bc29f3 [http://dl-cdn.alpinelinux.org/alpine/v3.7/main]
v3.7.3-183-gcc9ad2b48d [http://dl-cdn.alpinelinux.org/alpine/v3.7/community]
OK: 9066 distinct packages available
/ # apk add make g++ sqlite-dev
(1/19) Upgrading musl (1.1.18-r3 -> 1.1.18-r4)
(2/19) Installing libgcc (6.4.0-r5)
(3/19) Installing libstdc++ (6.4.0-r5)
(4/19) Installing binutils-libs (2.30-r2)
(5/19) Installing binutils (2.30-r2)
(6/19) Installing gmp (6.1.2-r1)
(7/19) Installing isl (0.18-r0)
(8/19) Installing libgomp (6.4.0-r5)
(9/19) Installing libatomic (6.4.0-r5)
(10/19) Installing mpfr3 (3.1.5-r1)
(11/19) Installing mpc1 (1.0.3-r1)
(12/19) Installing gcc (6.4.0-r5)
(13/19) Installing musl-dev (1.1.18-r4)
(14/19) Installing libc-dev (0.7.1-r0)
(15/19) Installing g++ (6.4.0-r5)
(16/19) Upgrading musl-utils (1.1.18-r3 -> 1.1.18-r4)
(17/19) Installing make (4.2.1-r0)
(18/19) Installing sqlite-libs (3.25.3-r2)
(19/19) Installing sqlite-dev (3.25.3-r2)
Executing busybox-1.27.2-r7.trigger
OK: 187 MiB in 47 packages
/ # gem install sqlite3
Fetching: sqlite3-1.4.2.gem (100%)
Building native extensions. This could take a while...
Successfully installed sqlite3-1.4.2
1 gem installed
/ #

それぞれ、足りない時のエラー

他の OS でも似たようなエラーが出る可能性があると思うので記載しておきます。

make がない

/ # gem install sqlite3
Fetching: sqlite3-1.4.2.gem (100%)
Building native extensions. This could take a while...
ERROR:  Error installing sqlite3:
    ERROR: Failed to build gem native extension.

    current directory: /usr/local/bundle/gems/sqlite3-1.4.2/ext/sqlite3
/usr/local/bin/ruby -r ./siteconf20200515-13-8rgmaw.rb extconf.rb
checking for sqlite3.h... yes
checking for pthread_create() in -lpthread... yes
checking for -ldl... yes
checking for sqlite3_libversion_number() in -lsqlite3... yes
checking for rb_proc_arity()... yes
checking for rb_integer_pack()... yes
checking for sqlite3_initialize()... yes
checking for sqlite3_backup_init()... yes
checking for sqlite3_column_database_name()... yes
checking for sqlite3_enable_load_extension()... yes
checking for sqlite3_load_extension()... yes
checking for sqlite3_open_v2()... yes
checking for sqlite3_prepare_v2()... yes
checking for sqlite3_int64 in sqlite3.h... yes
checking for sqlite3_uint64 in sqlite3.h... yes
creating Makefile

current directory: /usr/local/bundle/gems/sqlite3-1.4.2/ext/sqlite3
make "DESTDIR=" clean
sh: make: not found

current directory: /usr/local/bundle/gems/sqlite3-1.4.2/ext/sqlite3
make "DESTDIR="
sh: make: not found

make failed, exit code 127

g++ がない

/ # gem install sqlite3
Fetching: sqlite3-1.4.2.gem (100%)
Building native extensions. This could take a while...
ERROR:  Error installing sqlite3:
    ERROR: Failed to build gem native extension.

    current directory: /usr/local/bundle/gems/sqlite3-1.4.2/ext/sqlite3
/usr/local/bin/ruby -r ./siteconf20200515-10-llmpwx.rb extconf.rb
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
    --with-opt-dir
    --without-opt-dir
    --with-opt-include
    --without-opt-include=${opt-dir}/include
    --with-opt-lib
    --without-opt-lib=${opt-dir}/lib
    --with-make-prog
    --without-make-prog
    --srcdir=.
    --curdir
    --ruby=/usr/local/bin/$(RUBY_BASE_NAME)
    --with-sqlcipher
    --without-sqlcipher
    --with-sqlite3-config
    --without-sqlite3-config
    --with-pkg-config
    --without-pkg-config
/usr/local/lib/ruby/2.5.0/mkmf.rb:456:in `try_do': The compiler failed to generate an executable file. (RuntimeError)
You have to install development tools first.
    from /usr/local/lib/ruby/2.5.0/mkmf.rb:541:in `try_link0'
    from /usr/local/lib/ruby/2.5.0/mkmf.rb:559:in `try_link'
    from /usr/local/lib/ruby/2.5.0/mkmf.rb:660:in `try_ldflags'
    from /usr/local/lib/ruby/2.5.0/mkmf.rb:1820:in `pkg_config'
    from extconf.rb:35:in `<main>'

To see why this extension failed to compile, please check the mkmf.log which can be found here:

  /usr/local/bundle/extensions/x86_64-linux/2.5.0/sqlite3-1.4.2/mkmf.log

extconf failed, exit code 1

sqlite-dev がない

/ # gem install sqlite3
Fetching: sqlite3-1.4.2.gem (100%)
Building native extensions. This could take a while...
ERROR:  Error installing sqlite3:
    ERROR: Failed to build gem native extension.

    current directory: /usr/local/bundle/gems/sqlite3-1.4.2/ext/sqlite3
/usr/local/bin/ruby -r ./siteconf20200515-10-s4bcgw.rb extconf.rb
checking for sqlite3.h... no
sqlite3.h is missing. Try 'brew install sqlite3',
'yum install sqlite-devel' or 'apt-get install libsqlite3-dev'
and check your shared library search path (the
location where your sqlite3 shared library is located).
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
    --with-opt-dir
    --without-opt-dir
    --with-opt-include
    --without-opt-include=${opt-dir}/include
    --with-opt-lib
    --without-opt-lib=${opt-dir}/lib
    --with-make-prog
    --without-make-prog
    --srcdir=.
    --curdir
    --ruby=/usr/local/bin/$(RUBY_BASE_NAME)
    --with-sqlcipher
    --without-sqlcipher
    --with-sqlite3-config
    --without-sqlite3-config
    --with-pkg-config
    --without-pkg-config
    --with-sqlcipher
    --without-sqlcipher
    --with-sqlite3-dir
    --without-sqlite3-dir
    --with-sqlite3-include
    --without-sqlite3-include=${sqlite3-dir}/include
    --with-sqlite3-lib
    --without-sqlite3-lib=${sqlite3-dir}/lib

To see why this extension failed to compile, please check the mkmf.log which can be found here:

  /usr/local/bundle/extensions/x86_64-linux/2.5.0/sqlite3-1.4.2/mkmf.log

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

【Rails tutorial】 リスト 9.25: [remember me] チェックボックスのテストでエラーになる。【9章】

エラー内容

Rails tutorial9章 リスト9.25を実施時に以下エラーとなりました。

 FAIL["test_login_without_remembering", UsersLoginTest, 4.873426502104849]
 test_login_without_remembering#UsersLoginTest (4.87s)
        Expected "I8qYEjEm7MeNLQbqt4YM3A" to be empty.
        test/integration/users_login_test.rb:52:in `block in <class:UsersLoginTest>'

チェックボックスのオンオフが効いてないようです。
セッションのコントローラーについて確認してみると・・・

app/controllers/sessions_controller.rb
  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      params[:session][:remember_me] == '1' ? remember(user) : forget(user)
      remember(user)
      redirect_to user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

remember(user)を消し忘れていたことが原因のようです。
これだとparams[:session][:remember_me] == '0'の場合でもremember(user)メソッドを実行しちゃいますね。

remember(user)を消してテストを実施。
今度はテスト成功しました!

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

【最短】Ruby on Rails環境構築メモ

はじめに

はじめまして、Rubyを学習しはじめたおにくです!
今回はmacでのruby環境構築について学んだので、自分用にメモを残して行きたいと思います!

※こちらの環境構築はOSがmojaveの場合の環境構築になります

環境構築のフロー

1、Command Line Toolsのインストール

コマンドラインツールとは・・・「キーボードでPCを操作する道具」

ターミナルから以下のコマンドを実行
※ターミナルはmacに元々インストールされているツールです

xcode-select --install

以下のようなポップアップが表示されるのでインストールを行う
command-line-tools.png

2、Homebrewのインストール

homebrewとは・・・macでプログラミングに必要なパッケージをインストール、アンインストールするもの

ターミナルで以下のコマンドを実行する
※コマンドは以下のhomebrewの公式サイトに記載されています
macOS用パッケージマネージャー — Homebrew

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"

ターミナルで以下のコマンドを実行し、homebrewがインストールできたか確認

brew -v

3、rbenvとruby-buildのインストール

rbenvとは・・・rubyのインストール、アンインストールやバージョン管理を行うもの
ターミナルで以下のコマンドを実行しインストール

brew install rbenv ruby-build

rbenvをPCのどこからでも呼び出せるように以下のコマンドをターミナルにて実行

echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
source ~/.bash_profile

4、ruby2.5.1のインストール

ターミナルで以下のコマンドを実行

rbenv install 2.5.1
rbenv global 2.5.1
rebnv rehash
ruby -v

ここでruby2.5.1が表示されていればrubyがインストールできています

5、mysqlのインストール

mysqlとは・・・データを保存するためのデータベースサーバー
ターミナルで以下のコマンドを実行

brew install mysql

mysqlをPCのどこからでも呼び出せるように以下のコマンドをターミナルにて実行

echo 'export PATH="/usr/local/opt/mysql/bin:$PATH"' >> ~/.bash_profile
source ~/.bash_profile

6、bundlerのインストール

bundlerとは・・・gemの依存関係をバージョンを管理するためのツール
ターミナルで以下のコマンドを実行

gem install bundler

7、railsのインストール

ターミナルで以下のコマンドを実行

gem install rails --version='5.2.3'
rbenv rehash
rails -v

ここでrails5.2.3が表示されていればrailsがインストールできています

※もし、誤りなどありましたらコメントにてご教授いただけますと幸いです:bow:

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

編集画面で更新ができない!?

An invalid form control with name='◯◯[image]' is not focusable. というエラー

編集画面(edit)ではnewの画面をコピーして使用することもあるかと思います。
私は今回コピペで作成し、動作確認をしていました。

通常であれば問題なく動くはず。。。なのですがなぜか更新ボタンを押しても反応しない状態状態でした。
デベロッパーツールを見てみると、
An invalid form control with name='recipe[image]' is not focusable.
というエラーが出ていました。

スクリーンショット 2020-05-15 22.10.56.png

name='recipe[image]'のformコントローラーが使えない・・・?

該当コード

= form_with model: @recipe do |f|
  .new__main__upper-half__left#image_input
    = f.label :image, {class: 'new__main__upper-half__left__label'} do
      = f.file_field :image, {class: 'new__main__upper-half__left__label__input',required: "required"}
        %pre
          %i.fas.fa-camera.fa-lg
          クリックして画像を選んでください

解決方法

入力必須のオプションである required: "required" を消す、だけ

editで生成されるfile_fieldは上手く初期値が拾えていないようです。
その結果、入力必須のオプションを使用しているとrails様は
『必須になってるけどそんなんないよ!?』
と戸惑ってしまう分けです。

と言うわけで、file_fieldはrequired: "required"を付けないようにしましょう

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

Rails セキュリティについて

前提

セキュリティについて勉強した方が今後の為になると教えをいただいた為、学んだことを書いていきます。

本題

フレームワークは、Webアプリを容易に開発できるよう作られた。
Ruby on Railsには、セキュリティを保つための便利なヘルパーメソッドが用意されている。

だが、導入するだけでセキュリティを保てるものではない。

Gartner Groupによると、攻撃の75%がWebアプリケーション層に対して行われていると見積もっており、監査を受けた300のWebサイトのうち97%が脆弱性を抱えているという結果を得ているとのこと。
これは、Webアプリケーションに対する攻撃は比較的行いやすく、一般人も理解や操作が可能なほどにWebアプリケーションがシンプルであるため。

安全なWebアプリケーションを開発するために必要なことは、
・すべての階層を最新の状態に保つこと
・敵を知ること

セッション

セッションはある種の攻撃の対象になることがある。

セッションとは

アプリケーションはセッションを用いて、多くのユーザーがアプリケーションとやりとりできるようにしつつ、各ユーザー固有のステートを維持。
たとえば、、、
セッションを用いることで、ユーザーが認証を1回行うだけで以後のリクエストでサインインしたままにできる。

Railsでは、アプリケーションにアクセスするユーザーごとにセッションオブジェクトを1つ提供する。
ユーザーが既にアプリケーションを利用中であれば、Railsは既存のセッションを読み込み、その他の場合は新しいセッションを作成する。

セッションハイジャック

ユーザーのセッションIDが盗まれると、攻撃者がそのユーザーをかたってWebアプリケーションを利用できてしまう。

多くのWebアプリケーションには何らかの認証システムがある。
ユーザーがユーザー名とパスワードを入力すると、Webアプリケーションはそれらをチェックして、対応するユーザーIDをセッションハッシュに保存し、その後、そのセッションは有効になる。
リクエストが行われるたびに、Webアプリケーションはセッションで示されたユーザーidを持つユーザーを読み込む。
このときに再度認証を行なう必要はない。
セッションは、cookie内のセッションidによって識別できる為。

このように、cookieはWebアプリケーションに一時的な認証機能を提供している。
要するに、他人のcookieを奪い取れれば、その奪い取ったユーザーの権限でWebアプリケーションを使うことができる!

セッションハイジャックの手法・対策

セキュリティに不備のあるネットワークではcookieを覗き見することができる。
無線LANは、まさにそのようなネットワークの一例。
接続されているクライアントのすべてのトラフィックをのぞき見ることは、暗号化されていない無線LANでは特に簡単に行行える。
Webアプリケーションの開発者にとっては、これはSSLによる安全な接続の提供が必要である。
Rails 3.1以降では、アプリケーションの設定ファイルでSSL接続を強制することによって達成できる。

config.force_ssl = true

・よく目立つログアウトボタンを提供する
なぜなら、、、
わざわざcookieを消去するようなユーザーはいないため。
ログアウトを忘れてしまうと、次のユーザーがそのWebアプリをそのまま使えてしまうため。

クロスサイトスクリプティング (XSS) 攻撃は、多くの場合、ユーザーのcookieを手に入れるのが目的。

・攻撃者が自分の知らないcookieをわざわざ盗み取る代りに、自分が知っているcookieのセッションidを固定してしまうという攻撃方法がある

セッションストレージ

RailsはデフォルトのセッションストレージとしてActionDispatch::Session::CookieStoreを用いる。

RailsのCookieStoreはクライアント側のcookieにセッションハッシュを保存する。
サーバーはこのセッションハッシュをcookieから取得することで、セッションIDの必要性を解消。
こうすることで、アプリケーションのスピードは著しく向上するが、このストレージオプションについては議論の余地があるため、セキュリティ上の意味やストレージでの制約について以下の点を十分考えておかなければならない。

・cookieの上限は4KB。セッションに関連するデータを保存する目的でのみcookieを使う。

・cookieはクライアント側に保存される。
クライアントには、cookieの期限が切れた場合にもcookieの内容が残っていることがある。
また、クライアントのcookieが他のコンピュータにコピーされる可能性もある。
セキュリティ上重要なデータをcookieに保存することは避けるべき。

・cookieは本質的に一時的な情報である。
サーバーはcookieに期限を設定できるが、期限が切れる前にcookieやcookieの内容がクライアント側で削除される可能性がある。
恒常性の高いデータは、すべてサーバー側で永続化すべき。

・セッションcookieはひとりでに失効することはないため、悪用目的で使い回される可能性もある。
保存済みのタイムスタンプを利用して古いセッションcookieをアプリケーションで失効させるのもよい方法かも。

・Railsはcookieをデフォルトで暗号化する。
クライアントは暗号を解読しないかぎりcookieの内容を読み取ることも編集することもできない。
秘密情報を適切に扱っていれば、cookieのセキュリティは一般的に保たれていると考えてよい。

・CookieStoreはセッションデータの保管場所をencrypted cookie jarで安全に暗号化する。
これにより、cookieベースのセッションの内容の一貫性と機密性を同時に保つ。
暗号化鍵は、signed cookieに用いられる検証鍵と同様に、secret_key_base設定値から導出される。

秘密鍵は十分に長く、かつランダムなものにしなければならない。
一意な秘密鍵を得るにはrails secretを使用する。

暗号化済みcookieと署名済みcookieで使うsalt値を同じにしないことも重要。
複数のsalt設定に異なる値ではなく同じsalt値を使ってしまうと、別のセキュリティ機能で同じ鍵が導出されてしまい鍵の強度が落ちる可能性がある。

test環境とdevelopment環境のアプリケーションでは、アプリケーション名からsecret_key_baseを導出。
それ以外の環境では、必ずconfig/credentials.yml.encにあるランダムな鍵を使わなければならない。

CookieStoreセッションに対する再生攻撃

再生攻撃(replay attack)の仕組み
・ユーザーがクレジットを受け取る。総額はセッションに保存されているとする。
・ユーザーがクレジットで何かを購入する。
・つかった分減ったクレジットがセッションに保存される。
・ここでユーザーの暗黒面が発動。最初にブラウザに保存されていたcookieをコピーしてあったものを、現在のブラウザのcookieと差し替える。
・ユーザーのクレジット額が元に戻る。

この種のデータはセッションではなくデータベースに保存するのが最善。

この再生攻撃は、セッションにnonce (1回限りのランダムな値) を含めておくことで防ぐことができる。
nonceが有効なのは1回限りであり、サーバーはnonceが有効かどうかを常に追跡し続ける必要がある。
複数のアプリケーションサーバーで構成された合いの子アプリケーションの場合、状況はさらに複雑になる。
nonceをデータベースに保存してしまうと、せっかくデータベースへのアクセスを避けるために設置したCookieStoreを使う意味がなくなってしまう。

セッション固定攻撃

セッション固定 (session fixation) とは
ユーザーのセッションIDを盗む代りに、攻撃者が意図的にセッションIDを既知のものに固定するという方法。

この攻撃では、ブラウザ上のユーザーのセッションIDを攻撃者が知っているセッションidに密かに固定しておき、ブラウザを使うユーザーが気付かないうちにそのセッションIDを強制的に使わせる。
この方法であれば、セッションIDを盗み出す必要すらない。

セッション固定攻撃対策

最も効果的な対応策は、ログイン成功後に古いセッションを無効にし、新しいセッションidを発行すること。
これなら、攻撃者が固定セッションidを悪用する余地はない。
この対応策は、セッションハイジャックにも有効。

Railsで新しいセッションを作成する方法

reset_session

ユーザー管理用にDeviseなどのgemを導入している場合、ログイン・ログアウト時にセッションが自動的に切れるようになっている。
もし自分で管理する場合は、ログイン後 (セッションが作られた後) にセッションを切るように注意しなければならない。
上のメソッドを実行するとセッションにあるすべての値が削除されるため、それらの値を新しいセッションに転送しておく必要がある。

その他対応策
セッションにユーザー固有のプロパティを保存しておき、ユーザーからリクエストを受けるたびに照合して、マッチしない場合はアクセスを拒否するという方法。
ユーザー固有のプロパティとして利用可能な情報には、リモートIPアドレスや user agent (= webブラウザの名前) があるが、後者は完全にユーザー固有とは限らない。

IPアドレスを保存して対応する場合、インターネットサービスプロバイダ (ISP) や大企業からのアクセスはプロキシ越しに行われていることが多いことを思い出しておく必要がある。
IPアドレスはセッションの途中で変わる可能性があるため、IPアドレスをユーザー固有の情報として使おうとすると、ユーザーがWebアプリケーションにアクセスできなくなったり、ユーザーの利用に制限が加わる可能性がある。

セクションの期限切れ

セッションを無期限にすると、攻撃される機会を増やしてしまう (クロスサイトリクエストフォージェリ (CSRF)、セッションハイジャック、セッション固定など)。

セッションIDを持つcookieのタイムスタンプに有効期限を設定するという対応策も考えられなくはありません。
しかし、ブラウザ内に保存されているcookieをユーザーが編集できてしまう点は変わらないので、やはりサーバー側でセッションを期限切れにする方が安全。
データベーステーブルのセッションを期限切れにするには、たとえば次のようにSession.sweep("20 minutes")を呼ぶと、20分以上経過したセッションが期限切れになる。

class Session < ApplicationRecord
  def self.sweep(time = 1.hour)
    if time.is_a?(String)
      time = time.split.inject { |count, unit| count.to_i.send(unit) }
    end

    delete_all "updated_at < '#{time.ago.to_s(:db)}'"
  end
end

攻撃者が5分おきにセッションを維持すると、サーバー側でセッションを期限切れにしようとしてもセッションを恒久的に継続させることができてしまう。
これに対する単純な対策は、セッションテーブルにcreated_atカラムを追加すること。
これで、期限を過ぎたセッションを削除できる。
上のsweepメソッドで以下のコードを使用。

delete_all "updated_at < '#{time.ago.to_s(:db)}' OR
  created_at < '#{2.days.ago.to_s(:db)}'"

クロスサイトリクエストフォージェリ(CSRF)

この攻撃方法は、ユーザーによる認証が完了したと考えられるWebアプリケーションのページに、悪意のあるコードやリンクを仕込むというもの。
そのWebアプリケーションへのセッションがタイムアウトしていなければ、攻撃者は本来認証されていないはずのコマンドを実行できてしまう。

多くのRailsアプリケーションがcookieベースのセッションを使っている。
この時、セッションIDをcookieに保存してサーバー側にセッションハッシュを持つか、すべてのセッションハッシュをクライアント (ブラウザ) 側に持つ。
どちらの場合にも、ブラウザはリクエストのたびにcookieを自動的にドメインに送信する (そのドメインで利用できるcookieがある場合)。
ここで問題となるのは、異なるドメインに属するサイトからリクエストがあった場合にもブラウザがcookieを送信してしまうという点。

CSRFの対応方法

1、第一に、W3Cが要求しているとおり、GETとPOSTを適切に使う。
2、GET以外のリクエストにセキュリティトークンを追加することで、WebアプリケーションをCSRFから守ることができる。

HTTPプロトコルは2つの基本的なリクエストであるGETとPOST(DELETE、PUT、PATCHはPOSTと同様に使われるべきです)を提供している。
World Wide Web Consortium (W3C) は、HTTPのGETやPOSTを選択する際のチェックリストを提供している。

以下の場合はGET
・そのやりとりが基本的に問い合わせである場合 (クエリ、読み出し操作、検索のような安全な操作)

以下の場合はPOST
・そのやりとりが基本的に命令である場合
・そのやりとりによってユーザーにわかる形でリソースの状態が変わる場合 (サービスへの申し込みなど)
・そのやりとりによって生じる結果をユーザーが把握できる場合

WebアプリケーションがRESTfulであれば、PATCH、PUT、DELETEなどのHTTPメソッドも使われているだろう。
しかし、一部のブラウザはこれらのメソッドをサポートしていない。
確実にサポートされているのはGETとPOSTだけ。
Railsでは_methodという隠しフィールドを使ってこれらのHTTPメソッドをサポートしている。

POSTリクエストも (意図に反して) 自動的に送信されることがありえる。
ブラウザのステータスバーに、www.harmless.com というWebサイトへのリンクが表示されているとする。
そしてこのリンクに仕掛けがあり、POSTリクエストをこっそり送信する新しいフォームを動的に作成するようになっているとする。

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

AtCoder Beginners SelectionでRuby学習【Shift only】繰り返し処理と配列の上書き

はじめに

Ruby学習の一環として「競技プログラミング(競プロ)」に挑戦します。
そのための学習の中で学んだことをアウトプットしていきます。
今回は「AtCoder Beginners Selection」の四問目(Shift only)より。
https://atcoder.jp/contests/abs

自分が使った解法と、他の方の解法を比べながらまとめていきます。

問題

N個の正の整数A1,...,ANが与えられます。
この整数が全て偶数である時、次の操作を行います。
・整数全てを2で割ったものに置き換える。
最大何回操作を行えるか求めなさい。

制約
1 ≤ N ≤ 200
1 ≤ Ai ≤ 10**9

入力は以下の形で与えられる。

N
A1 A2 ... AN

# 例
3
8 12 40

上記の例だと最大2回割ることができるので

出力例
# 上記例の場合
=> 2

解答①

まずは僕が最初に書いたコードです。

count = 0
N = gets.to_i
lists = gets.split(" ").map(&:to_i)

while lists.all?(&:even?) do
    lists.map!{ |x| x / 2 }
    count += 1
end

print count

第一回の問題で学んだメソッド(gets split map)を使って入力を受け取り、
AtCoder Beginners SelectionでRuby学習【PracticeA】標準入力
第二回で学んだメソッド(all? even?)で判定を行いつつ、
AtCoder Beginners SelectionでRuby学習【Product】様々な解法から学ぶ
while文やmap!メソッドを使って解答しました。

まずは、ここで初めて使ったwhile文、map!メソッドについてまとめていきます。

while文

条件式が真である間は、指定した処理を繰り返します。

while 条件式 do
  実行する処理1
  実行する処理2
end

今回の解答では、偶数判定を行う「even?メソッド」を「all?メソッド」を使って配列の要素全てにあて、
全て偶数である場合には、指定した処理を行うようにしました。

map!メソッド

配列の要素を、処理結果で上書きします。

配列の入った変数.map! { |変数名| 処理内容 }

#例
lists = [10, 20]
lists.map!{ |x| x / 2 }
print lists
=> [5, 10]

ちなみに、
入力の時に使用しているmapメソッドでは、
配列の上書きは行われません。戻り値として配列を生成しています。

#例
lists = [10, 20]
print lists.map{ |x| x / 2 }
=> [5, 10]
print lists
=> [10, 20]

以下は他の方の解答になります。

解答② mapメソッドで操作の回数(解答)を戻り値として戻す

配列の要素を「2で割れる回数」にして配列として戻し、
その要素の中で最も小さい数を「minメソッド」を使って出力しています。

gets
puts gets.split(" ").map{|f|
    f=f.to_i
    c=0
    while f%2==0
        f/=2
        c+=1
    end
    c
}.min

minメソッド(Arrayクラス)

配列の最小の要素を戻します。

#例
lists = [35, 10, 20]
print lists.min
=> 10

最後に

以上、AtCoder Beginners SelectionでRuby学習【Shift only】から学んだメソッドをご紹介しました。
他の方の解答も見れるのが便利ですね。かなり勉強になります。

もし間違いなどございましたら、ご指摘いただけると嬉しいです。

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

nokogiriを更新したらRubyを実行するたびに「Ignoring」と毎回怒られるようになった

nokogiriを更新すると毎回このようにIgnoringが出てくるようになった。

Ignoring childprocess-2.0.0 because its extensions are not built. Try: gem pristine childprocess --version 2.0.0
Ignoring childprocess-1.0.1 because its extensions are not built. Try: gem pristine childprocess --version 1.0.1
Ignoring ed25519-1.2.4 because its extensions are not built. Try: gem pristine ed25519 --version 1.2.4
Ignoring escape_utils-1.1.1 because its extensions are not built. Try: gem pristine escape_utils --version 1.1.1
Ignoring ffi-1.11.3 because its extensions are not built. Try: gem pristine ffi --version 1.11.3
Ignoring ffi-1.11.2 because its extensions are not built. Try: gem pristine ffi --version 1.11.2
Ignoring ffi-1.11.1 because its extensions are not built. Try: gem pristine ffi --version 1.11.1
Ignoring ffi-1.11.0 because its extensions are not built. Try: gem pristine ffi --version 1.11.0
Ignoring ffi-1.9.25 because its extensions are not built. Try: gem pristine ffi --version 1.9.25
Ignoring ffi-1.9.23 because its extensions are not built. Try: gem pristine ffi --version 1.9.23
Ignoring ffi-1.9.18 because its extensions are not built. Try: gem pristine ffi --version 1.9.18
Ignoring greenmat-3.2.2.4 because its extensions are not built. Try: gem pristine greenmat --version 3.2.2.4
Ignoring json-2.2.0 because its extensions are not built. Try: gem pristine json --version 2.2.0
Ignoring json-1.8.6 because its extensions are not built. Try: gem pristine json --version 1.8.6
Ignoring kgio-2.11.3 because its extensions are not built. Try: gem pristine kgio --version 2.11.3
Ignoring kgio-2.11.2 because its extensions are not built. Try: gem pristine kgio --version 2.11.2
Ignoring msgpack-1.3.1 because its extensions are not built. Try: gem pristine msgpack --version 1.3.1
Ignoring msgpack-1.3.0 because its extensions are not built. Try: gem pristine msgpack --version 1.3.0
Ignoring msgpack-1.2.10 because its extensions are not built. Try: gem pristine msgpack --version 1.2.10
Ignoring msgpack-1.2.6 because its extensions are not built. Try: gem pristine msgpack --version 1.2.6
Ignoring msgpack-1.2.4 because its extensions are not built. Try: gem pristine msgpack --version 1.2.4
Ignoring mysql2-0.5.1 because its extensions are not built. Try: gem pristine mysql2 --version 0.5.1
Ignoring mysql2-0.4.10 because its extensions are not built. Try: gem pristine mysql2 --version 0.4.10
Ignoring mysql2-0.3.20 because its extensions are not built. Try: gem pristine mysql2 --version 0.3.20
Ignoring mysql2-0.3.18 because its extensions are not built. Try: gem pristine mysql2 --version 0.3.18

解決策

gem pristine --all
を複数回、Igonorが消えるまで実行する。

途中でmysqlに関するエラーがでたが、アンインストールし、再インストールすると解消された

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

uniqueness: scope を使ったユニーク制約方法の解説

uniqueness: scope を使ったユニーク制約方法の解説

uniqueness: scopeを利用して一意性検証をする方法について解説します。

目次


動作環境

OS : macOS Mojave 10.14.6
ruby : 2.6.3p62
rails : 5.2.4

実装例

class Label < ApplicationRecord
  has_many :labelings, dependent: :destroy
  belongs_to :user, optional: true
  validates :name, presence: true, uniqueness: { scope: :user }
end

ユーザはタスクに紐付けるラベルを作成することができますが、各ユーザは同じ名前のラベルを作れないようにラベルモデルのnameカラムに一意性制約をつけています。

実行結果[5]を見るとRollbackしています.

[3] pry(main)> user = User.first
[4] pry(main)> user.labels.create(name:'test-label')
   (0.2ms)  BEGIN
   (1.6ms)  COMMIT
#同じユーザで同名のラベルを作成する
[5] pry(main)> user.labels.create(name:'test-label')
   #Rollbackする
   (0.3ms)ROLLBACK
#違うユーザで検証
[6] pry(main)> user2 = User.last
[7] pry(main)> user2.labels.create(name:'test-label')
   (5.7ms)  BEGIN
#書き込み成功
  (34.0ms) COMMIT

解説

scopeを付けない場合,テーブル全体で一つの名前のラベル名しか保存できません。
validates :name, uniqueness:true

つまりscopeという文字通りscopeの中での一意性制約にするオプションです。

このことによって各ユーザごとに一意となるカテゴリを作成することができます。

複数のscope

またscopeは配列により複数作成することもできます。

scope は配列にして複数指定できます。

validates :name, uniqueness: { scope: [:group_id, :user_id] }
これでname, group_id, user_id の全てが同じデータは1件しか作成できないように制約できます.

データベース側の制約

また上記だけでなくデータベース側にも制約を作成する場合は、以下のように両方のカラムにuniqueインデックスを作成します.

label.rb
class AddUniqueIndexToLabels < ActiveRecord::Migration
  def change
    add_index :labels, [:name, :user_id], unique: true
  end
end

おわりに

ActiveRecordの一意性検証の範囲指定について学びました.

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

GitHubとアプリケーションの紐付け方-チーム開発-

概要

この記事ではGitHubとアプリケーションの紐付け方を解説します。
また、その際にGitHub Desktopを活用していきます。

方法

まず、ローカルリポジトリを作成します。

GitHub Desktopを開いて左上のCurrent Ripositoryをクリックします。
クリックして開いた画面のAddをクリックします。
スクリーンショット 2020-05-15 20.59.55.png
すると下記のような画面が出てきます。

スクリーンショット 2020-05-15 21.00.12.png
その画面内のChoose...をクリックするとファイルが選択できる画面が出てくるので自分が作成中のアプリケーションのフォルダを選択します。
そしてAdd Repositoryボタンをクリックします。
これでローカルリポジトリの作成完了です。

もしAdd repositoryが押せない場合はターミナルの自分が作成中のディレクトリでgit initを叩いてください。
そうすればクリックできるようになると思います。

スクリーンショット 2020-05-15 21.00.31.png

次に上記画面の左下にSummary(required)とあると思いますので、そこにinitial commit(初めてのコミットという意味)と入力して、Commit to masterボタンをクリックします。
これでコミットが完了したので最後ににリモートリポジトリを作成します。

画面右上にPublic repositoryがあるのでそこをクリックします。するとリポジトリ名を決められたり、コードを公開するかしないかのチェックボックスが出てきます。場合に合わせて適宜変更してください。
Publish repositoryボタンをクリックしたらリモートレポジトリの作成完了です。
ブラウザでGithubのページにアクセスして自分の作成しているアプリケーションの名前があれば作成は成功しています。

ここまででGitHubとアプリケーションの紐付けに関しては完了しています。





この記事を読んでいただきありがとうございました。

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

【ruby】『ruby入門(メモ6)』メソッドで恐竜fizzbuzzを作ってみよう(windows10+ruby2.3.3)

0.あると望ましい(どうでもいい)前提知識

超久しぶりの投稿!
気分転換のためにfizzbuzzを作成してみました。
インドミナスレックス(IndominusRex):ヴェロキラプトルとティラノザウルスを融合させた恐竜。
インドラプトル(Indoraptor):インドミナスレックスとヴェロキラプトルを融合させた恐竜。

この知識を前提に変形fizzbuzzを作ってみた。
最後にインドラプトルが表示されていれば成功!

1.コード

method_fizzbuzz.rb
def act_dino(dinosor)
    if dinosor == 'Tyrannosaurus'
        puts 'Bite!!'
    elsif dinosor == 'Stegosaurus'
        puts 'Waving the tail!'
    else
        puts '???'
    end             
end

def FizzBuzz_dinosor(n)
    #インドラプトル
    if n % 45 == 0
        'Indoraptor'
    #インドミナスレックス 
    elsif n % 15 == 0
        'IndominusRex'
    #ティラノサウルス
    elsif n % 5 == 0
        'Tyrannosaurus'
    #ヴェロキラプトル   
    elsif n % 3 == 0
        'Velociraptor'
    else
        n.to_s
    end
end

act_dino("tiger")
act_dino("Stegosaurus")

n=0
while n < 45 do
    n += 1
    puts FizzBuzz_dinosor(n)
end

2.実行結果

c:\ruby_pg>ruby method_fizzbuzz.rb
???
Waving the tail!
1
2
Velociraptor
4
Tyrannosaurus
Velociraptor
7
8
Velociraptor
Tyrannosaurus
11
Velociraptor
13
14
IndominusRex
16
17
Velociraptor
19
Tyrannosaurus
Velociraptor
22
23
Velociraptor
Tyrannosaurus
26
Velociraptor
28
29
IndominusRex
31
32
Velociraptor
34
Tyrannosaurus
Velociraptor
37
38
Velociraptor
Tyrannosaurus
41
Velociraptor
43
44
Indoraptor

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

AtCoder Beginners SelectionでRuby学習【Product】様々な解法から学ぶ

はじめに

Ruby学習の一環として「競技プログラミング(競プロ)」に挑戦します。
そのための学習の中で学んだことをアウトプットしていきます。
今回は「AtCoder Beginners Selection」の二問目(Product)より。
https://atcoder.jp/contests/abs

問題

2つの正整数a,bが与えられます。
aとbの積が偶数か奇数か判定してください。

制約
1≤a,b≤10,000
aとbは整数

入力は以下の形で与えられる。

a b

# 例
2 3

積が奇数なら Odd 、偶数なら Even と出力せよ。

出力例
# 上記例の場合
Even

解答①

まずは僕が最初に書いたコードです。

a,b = gets.chomp.split(" ").map(&:to_i)

c = a * b

if c % 2 == 0 
    print "Even"
else
    print "Odd"
end

前回の学習で学んだメソッド(gets chomp split map)を使って入力を受け取り、
あとはif文で…という感じ。
AtCoder Beginners SelectionでRuby学習【PracticeA】標準入力

ただ他の方の解答を見てみると、2行で完結している方もいて、そっちの方がかっこいい!
ので、どんな記法を用いて書いているのか、いくつか見てみたいと思います。

解答② 三項演算子

a, b = gets.chomp.split.map(&:to_i)
(a*b%2==0) ? puts('Even') : puts('Odd')

入力を受け取るまではほぼ同じで、
後半は三項演算子を用いた記述方法ですね。
条件が複雑でない場合は、こちらの方が良さそうです。

三項演算子
条件式 ? 真の時の値 : 偽の時の値

解答③ odd?メソッド even?メソッド

a,b = gets.split(" ").map(&:to_i)
(a*b).even? ? (puts "Even") : (puts "Odd")

解答②で学んだ三項演算子を使いつつ、
代数演算子「%」ではなく、aとbの積をodd?メソッドもしくはeven?メソッドを使って
直接判定しちゃおうという方法。


#even?メソッド
10.even?
=> true

#odd?メソッド
10.odd?
=> false

ここから下は1行で記述が完結する解答です。
ざっとみた感じ、共通する考え方は「aかbのどっちかが偶数なら結果も偶数じゃん」ということ。
積ではなく、入力された整数について判定を行っています。

解答④ all?メソッド

all?メソッドは配列の要素を繰り返し実行し、返り値が全て真の時にtrueを返します。

puts gets.split.map(&:to_i).all?(&:odd?) ? "Odd" : "Even"

奇数を判定するodd?メソッドで配列の要素の判定を行い、
全て奇数であれば、trueを返すため"Odd"、一つでも偶数があれば、falseを返し、"Even"となります。

最後に最も短いコードでの解答です。

解答⑤ 正規表現

puts gets[/[02468]\b/]?:Even: :Odd

詳しい説明は今の僕には難しいので省きますが、
ここでは入力されてくる様々な文字列のパターンのうち
「末尾が偶数のもの」をメタ文字も用いつつまとめて表現し判定を行っているようです。

以下が正規表現で表現された部分

[/[02468]\b/]

ここで使われているメタ文字の意味は以下の通り。

メタ文字 意味          解答における役割 
[...] 括弧内のいずれか 1 文字 括弧内のいずれか 1 文字
/.../ 囲まれた文字列を正規表現とする 囲まれた文字列を正規表現とする
\b    先頭もしくは末尾とマッチ  解答例では末尾とマッチ

最後に

以上、AtCoder Beginners SelectionでRuby学習【Product】から学んだ様々な記法をご紹介しました。
コードの長さやメモリも判定されるみたいなので、簡潔に短い解答を意識していきたいですね。
もし間違いなどございましたら、ご指摘いただけると嬉しいです。

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

Cent OS を利用してrbenv で Rubyのダウンロードに失敗

rbenvを利用してrubyをダウンロードしようとしても
no such command install
と出てきてしまい失敗する。
rbenvはダウンロードされているようなのですがどうしてでしょうか。。。。
image.png

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

No space left on deviceが発生したとき

No space left on device

このエラーは端末(サーバー)の容量がいっぱいの時に起こるエラーです。

まず容量を確認するために以下のコードを記述する。

df -h
#=>Filesystem      Size  Used Avail Use% Mounted on
   devtmpfs        483M   60K  483M   1% /dev
   tmpfs           493M     0  493M   0% /dev/shm
   /dev/xvda1      9.8G  9.6G   36M 100% /

useが100%になって、ファイルが圧迫されていることがわかる。

不要なファイルを削除しましょう

du -sh ./* 
#=>
4.0K    ./,,,
60K     ./,,,,
832K    ./,,,,,,
54M     ./
1.4M    ./
304K    .
12M     ./
9.0M    ./
4.0K    ./
4.0K    ./
4.0K    ./
4.0K    ./
11M     ./
1.6M    ./
700K    ./
752K    ./
16K     ./

上記のようにどのファイルが、どれくらいの容量をくっているのかわかります。
いらないログファイルや、いらないファイルは削除しましょう。

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

railsアプリの作成手順

①rails new

rails newコマンドは、Railsアプリケーションの土台を作るためのコマンド

$ rails _5.2.4_ new sample -d postgresql

②rails server

rails severはwebサーバーを起動させるコマンド

$ rails db:create

③rails new scaffold

簡単なCRUD機能をもつアプリを作成するコマンド

$ rails generate scaffold モデル名 カラム名:データ型

作成時にGemfileからjbuilder gemを削除するとJSON関連のコードを生成しない。

gem 'jbuilder

参考記事
https://qiita.com/jnchito/items/ec070f7551c983cc5b60

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

部分テンプレート

部分テンプレートとは

viewファイルで同じHTML構造の部分を共通化したもの

例えばwebサイトのヘッダー部分や、ブログ記事の一覧表示画面の各投稿のレイアウトなども多くが共通していますよね

使用するメリット

・共通化することで何度も同じコードを書く必要がない

・修正する際の修正箇所が少なく済む

・他のviewファイルで使い回すことができる

使用方法

まずは共通化している部分を部分テンプレートのファイルに書き換えます。

ファイル名の前には必ず「 _ (アンダーバー)」を書きます

「_post.html.erb」 など。

あとは呼び出しをするだけですが、この際 render メソッドを使用します。

index.html.erb
<% render partial: "post" %>

partial オプションは部分テンプレート名の指定をしています。

また、部分テンプレート内で変数を扱いたい場合は locals オプションを使用します。

<% render partial: "部分テンプレート名", locals: { 変数: 値 } %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

deviseによるユーザー管理

deviceとは

Rubyのライブラリ(拡張機能)であるgemの一つ。

アプリケーションにおけるユーザ-新規登録・ログイン機能を簡単に実装できる。

導入方法

Gemfileに記載

gem 'devise' 

ターミナルのアプリケーションのディレクトリにて

bundle install

deviseの設定ファイルの作成

rails g devise:install

deviseのモデル作成用コマンドでuserモデルを作成

このコマンドで、ファイルの新規作成と、ユーザーのログイン・新規登録で必要なルーティングが生成される(routes.rbにdevise_for :usersが自動追記されるため)

rails g devise user

マイグレーションファイルの実行

rails db:migrate

deviseに対応したビューファイルの作成

これでシンプルな見た目の新規登録画面とログイン画面が生成されます

新規登録画面はapp/views/devise/registrations/new.html.erb、
ログイン画面のビューはapp/views/devise/sessions/new.html.erb が対応しています。

rails g devise:views

以上で新規ユーザー登録とログイン機能の基本的な実装はできます。

deviseを導入することで使用できるメソッドや、deviseによるユーザー登録情報の項目を追加したい場合(現状ではEメールと、パスワードのみの登録しかできない)は調べてみて下さい

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

【Rails】ActiveRecord::PendingMigrationErrorが出たときの対処法

エラー内容

ActiveRecord::PendingMigrationError
image.png

環境

  • Rails 6.0.3
  • psql (PostgreSQL) 12.2

概要

  • このアプリケーションに対応するマイグレーションファイルをmigrateし忘れてますよ、というエラー
  • 再度migrateすることで解決

原因

  1. db:create後にmigrateし忘れている
  2. すでに以前同じ名前でRailsアプリを作成とmigrateまでしていて、(何らかの事情で)アプリを消して、再度同じアプリ名で作成してmigrateしようとしたらエラーが発生

対策

1. db:create後にmigrateし忘れている

この場合は作成したアプリをbundle exec rake db:createでDB作成後にmigrateし忘れているだけなので(マイグレーションファイルを変更するだけではmigrateされない)、

bundle exec rake db:migrate

をターミナルで入力することで解決

2.すでに以前同じ名前でRailsアプリを作成とmigrateまでしていて、(何らかの事情で)アプリを消して、再度同じアプリ名で作成してmigrateしようとしたらエラーが発生

私はこのパターンで「アプリ消してるのに何でmigrateできないの?」とちょっと戸惑っていました?

Migrationファイル自体は(同じ名前なので)存在するが、今回作成したアプリとMigrationファイルが関連づけられていないことが原因なので、一度すでに作成されていたMigrationファイルを下記コマンドでリセットします。

rails db:migrate:reset

migrateをリセットできたので、再度migrateします。

bundle exec rake db:migrate

サーバーを再起動すれば解決です!

rails s

余談ですが、db:createdb:migrateなどデータベース関連の更新でうまくいかない時はサーバーをCtrl+Cで閉じてからやることをおすすめします(結構このパターンでのエラーもあるので注意)。

※migrateの状態が分からない時に調べるコマンド
rails db:migrate:status
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

検索機能の実装(form_tag・ransack)

検索機能の実装

form_tagとransackをそれぞれ用いた2種類の検索機能の実装を行いました。
特にこだわりがなければransackを用いたほうが簡単に実装できると感じました。

form_tagを用いた検索機能の実装

タスク一覧が表示されるtasks#indexページに、検索フォームを実装しました。
検索フォームに入力されたキーワードがデーターベースにあるタスク名と部分一致した場合、そのタスクを表示されるようにしました。
(html.slim, bootstrapを用いました。)

views/tasks/index.html.slim
h1 タスク一覧

= link_to '新規登録', new_task_path, class: 'btn btn-primary'

= form_with :method => "get", local: true do |f|
  .form-group
    = f.submit "Search", :name => nil ,class: "btn btn-outline-primary"
    = f.text_field :search

.mb-3
table.table.table-hover
  thead.thead-default
    tr
      th= '名称'
      th= '詳細'
  tbody
    - @tasks.each do |task|
      tr
        td= task.name
        td= task.description
        td= task.created_at

検索フォームで入力されたキーワードが引数としてparamsの中に入るように、
taskscontrollerのindexにsearch(params[:search])を追加しました。

controllers/tasks_controller.rb
def index
    @tasks = Task.search(params[:search])
end

最後にtaskモデルにsearchというメソッドを作成しました。
検索フォームで入力されたキーワードがsearchメソッドの中で処理され、
キーワードと部分一致するタスクがあれば、その結果がページに表示されます。
部分一致するタスク名がない場合は、allとして全てのタスクが一覧として表示されます。

models/task.rb
class Task < ApplicationRecord
  def self.search(search)
    if search
      where(["name LIKE ?", "%#{search}%"])
    else
      all
    end
  end
end

ransackを用いた検索機能の実装

簡単なタスクアプリの一覧ページに実装しました。
(html.slim, bootstrapを用いました。)

Gemfileにransackを追加し、ターミナルでbundle installしました。

Gemfile
gem 'ransack' #1番下でOK

検索に引っかかったタスクのみ表示するため、
indexメソッドを以下に書き換えました。

controllers/tasks_controller.rb
def index
  @q = User.ransack(params[:q])
  @users = @q.result(distinct: true)
end

indexのviewsを書き換えました。

views/tasks/index.html.slim
= search_form_for @q, class: 'mb-5' do |f|
  .form-group.row
    = f.label :name_cont, '名称', class: 'col-sm-2 col-form-label'
    .col-sm-10
      = f.search_field :name_cont, class: 'form-control'
  .form-group
    = f.submit class: 'btn btn-outline-primary'

また、ransackにはソート機能もあるため、並べ替えも行いたい場合はransackをお勧めします。

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

【Rails】has_manyの数を制限するvalidate

例えば、Userが持てるPostの数を制限したいときは、Post側にvalidateを設定します。

user.rb
class User < ActiveRecord::Base
  has_many :posts, dependent: :destroy
end
post.rb
class Post < ActiveRecord::Base
  MAX_POSTS_COUNT = 5

  belongs_to :user

  validate :posts_count_must_be_within_limit

  private

    def posts_count_must_be_within_limit
      errors.add(:base, "posts count limit: #{MAX_POSTS_COUNT}") if user.posts.count >= MAX_POSTS_COUNT
    end
end

これで、6個目のPostを作成しようとするとエラーになります。

ちなみに、Userにvalidateを定義した場合はUserをsaveしたときにvalidateが走ります。
つまり、Postのsave時にvalidateが効かず、6個目のPostを作れてしまうのでNGです。

user.rb
class User < ActiveRecord::Base
  has_many :posts, dependent: :destroy

  validates :posts, length: { maximum: 5 } # ←これでは`user.posts.create`のときに動かず、6個目のPostを作成できてしまう
end

環境

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

#Ruby 配列/ハッシュでぼっち演算子を利用して nil エラーを防ぐ ( NoMethodError: undefined method `[]' for nil:NilClass )

[] メソッド対して引数を与えれば良い

Array

[:a, :b, :c][0]
# => :a

nil[0]
# NoMethodError: undefined method `[]' for nil:NilClass

nil&.[](0)
# nil

Hash

{a: :x}[:a]
# => :x

nil[:a]
# NoMethodError: undefined method `[]' for nil:NilClass

nil&.[](:a)
# nil

Original by Github issue

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

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

Rubyでチンチロゲームを作る  第2回 勝敗判定

1.はじめに

 前回にひきつづきチンチロゲームをつくります。今回は自分と相手の役を比較し、どちらが強いかを判定させます。

2.勝敗判定の概要

 登場する役を書き出すとこんなかんじになります。

(最弱) ヒフミ < 目なし < 通常の目(1)...通常の目(6) < シゴロ < ゾロ目 < ピンゾロ (最強)

 サイコロを3つ振ってそれぞれの目が「1,2,3」となるヒフミが最弱。すべての目が1のピンゾロが最強となっています。

 勝敗判定は自分と相手の役を引数とし、勝ち負けを出力させます。やってみましょう。

3.メソッドの作成

 はじめにメソッドをこんな感じで考えました。

def check_win_lose(my_hand,opponent_hand)
    strength_relationship = [
      'ヒフミ','目なし',
      '通常の目(1)','通常の目(2)','通常の目(3)',
      '通常の目(4)','通常の目(5)','通常の目(6)',
      'シゴロ','ゾロ目','ピンゾロ'
    ]
    my_hand_rank = strength_relationship.index(my_hand)
    opponent_hand_rank = strength_relationship.index(opponent_hand)

    if(my_hand_rank > opponent_hand_rank)
       '勝ち' 
     elsif(my_hand_rank == opponent_hand_rank)
       '引き分け'
     else
       '負け'
     end
  end

strength_relationship に役を弱い順に入れておき、indexで強弱関係を判定します。やっていることは単純ですね。

 これでできました。

4.メソッドの改良

4-1.Playerクラスの作成

 でもこれだとあまりよくなさそうです。メソッドの引数の順序によって、自分と相手の役を逆にしてしまうおそれがあります。あと、今後の賭け金の移動のことなども考えるとクラスをつくったほうがいい気もします。
ちょっとやってみましょう。

 まずはPlayerクラスを作成します。

class Player
  attr_reader :name
  attr_accessor :money,:bet_money,:hand
  def initialize(**params)
    @name = params[:name]
    @money = params[:money]
    @bet_money = params[:bet_money]
    @hand = params[:hand]
  end
end

 クラスには名前、所持金、賭け金、役を入れておきます。
ためにしにカイジと班長をつくってみましょう。さきほどのコードの下に追記します。

player_A = Player.new(money:1000,bet_money:100,hand:'目なし',name:'カイジ')
player_B = Player.new(money:3000,bet_money:300,hand:'目なし',name:'班長')
puts <<~TEXT
名前: #{player_A.name} 
 所持金:#{player_A.money} ペリカ
 賭け金:#{player_A.bet_money} ペリカ
 役: #{player_A.hand}
--------------------------
名前: #{player_B.name} 
 所持金:#{player_B.money} ペリカ
 賭け金:#{player_B.bet_money} ペリカ
 役: #{player_B.hand}
TEXT

実行してみると以下のように表示されるはずです。

名前: カイジ 
 所持金:1000 ペリカ
 賭け金:100 ペリカ
 役: 目なし
--------------------------
名前: 班長 
 所持金:3000 ペリカ
 賭け金:300 ペリカ
 役: 目なし

無事にクラスが作成されていますね。班長の所持金は多めです。

4-2. クラス内のメソッド作成

 次に作成したクラスの中にメソッドを書いていきます。

class Player
  attr_reader :name
 # 略.....

  def check_win_lose(opponent)

    strength_relationship = [
      'ヒフミ','目なし',
      '通常の目(1)','通常の目(2)','通常の目(3)',
      '通常の目(4)','通常の目(5)','通常の目(6)',
      'シゴロ','ゾロ目','ピンゾロ'
    ]
    my_hand_rank = strength_relationship.index(@hand)
    opponent_hand_rank = strength_relationship.index(opponent.hand)

    if(my_hand_rank > opponent_hand_rank)
       '勝ち' 
     elsif(my_hand_rank == opponent_hand_rank)
       '引き分け'
     else
       '負け'
     end
  end
  # 略....
end

ちょっと引数が変わっていますね。 まず、このメソッドは以下のように呼び出します。

 player_A.check_win_lose(player_B)

 先頭のplayer_Aの役が@hand、player_Bの役がopponent.handに格納されています。そして上記だけではなにも表示されないので、あたまに表示させるメソッド pをつけます。

 p player_A.check_win_lose(player_B)

 これで、勝ち/負け/引き分けが表示されるはずです。

5.テストコード

 最後にテストをしましょう。前回にひきつづきminitestを利用します。(本当はRSpecをやってみたいですが勉強できてないので…) 今までのコードとは別ファイルにテストファイルを作成します。位置はこんなかんじです。

-lib
--transfer_money.rb
-test
--check_win_lose.rb

 やっていきましょう。

require 'minitest/autorun'
require './lib/transfer_money'
class DiceTest < Minitest::Test
  def test_win_lose
    roll_map = [
      'ヒフミ','目なし',
      '通常の目(1)','通常の目(2)','通常の目(3)',
      '通常の目(4)','通常の目(5)','通常の目(6)',
      'シゴロ','ゾロ目','ピンゾロ'
    ]
    win_lose_map = [
      ['引き分け','負け','負け','負け','負け','負け','負け','負け','負け','負け','負け'],
      ['勝ち','引き分け','負け','負け','負け','負け','負け','負け','負け','負け','負け'],
      ['勝ち','勝ち','引き分け','負け','負け','負け','負け','負け','負け','負け','負け'],
      ['勝ち','勝ち','勝ち','引き分け','負け','負け','負け','負け','負け','負け','負け'],
      ['勝ち','勝ち','勝ち','勝ち','引き分け','負け','負け','負け','負け','負け','負け'],
      ['勝ち','勝ち','勝ち','勝ち','勝ち','引き分け','負け','負け','負け','負け','負け'],
      ['勝ち','勝ち','勝ち','勝ち','勝ち','勝ち','引き分け','負け','負け','負け','負け'],
      ['勝ち','勝ち','勝ち','勝ち','勝ち','勝ち','勝ち','引き分け','負け','負け','負け'],
      ['勝ち','勝ち','勝ち','勝ち','勝ち','勝ち','勝ち','勝ち','引き分け','負け','負け'],
      ['勝ち','勝ち','勝ち','勝ち','勝ち','勝ち','勝ち','勝ち','勝ち','引き分け','負け'],
      ['勝ち','勝ち','勝ち','勝ち','勝ち','勝ち','勝ち','勝ち','勝ち','勝ち','引き分け']
    ]

    player_A = Player.new(hand:'目なし',name:'カイジ')
    player_B = Player.new(hand:'目なし',name:'班長')
    player_A.hand = roll_map[0]
    player_B.hand = roll_map[1]
    assert_equal win_lose_map[0][1], player_A.check_win_lose(player_B)
  end
end

 急に大きな配列があってびっくりしたかと思いますが、ただの勝敗表です。前回の反省を踏まえて、少しでもテストを楽にしようとあらかじめ表をつくりました。これをloopで回します。
 まずはひとつだけテストします。無事に通ったら以下のやつにつけかえてテストします。

   player_A = Player.new(hand:'目なし',name:'カイジ')
   player_B = Player.new(hand:'目なし',name:'班長')
   roll_map.each_with_index do |value_1,i|
       player_A.hand = value_1
       roll_map.each_with_index do |value_2,j|
         player_B.hand = value_2
         assert_equal win_lose_map[i][j], player_A.check_win_lose(player_B)
       end
   end

 あとはテストをします。無事に通りました!!!

6.おわりに

 次は勝敗結果に基づいて賭け金を移動させるメソッドをつくります!

 今回のコードは以下のリポジトリにあります。どんどん追記していきます!

https://github.com/kyokucho1989/ruby-game

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

Railsチュートリアル学習記録(第1章)

※チュートリアル2周目。開発環境はAtomを使用しています。
※頭の整理と備忘を兼ねて、学習内容を記録しています。
※表現の厳密さや精緻さよりも、直感的に理解しやすいよう記録しています。

・第1章でやりたいこと
⇨開発環境(アプリを作っている場所)で、「Hello World」という文章を表示させるプログラムを作成し、本番環境(アプリを公開する場所)に公開する。

・最初にRailsをダウンロードする。「-v 5.1.6」でバージョンを指定している。バージョン毎に機能が異なったりするので気をつける。

filename.rb
$ gem install rails -v 5.1.6

・アプリ開発に必要なファイル等を一括ダウンロード(ここでもバージョン指定)

filename.rb
$ rails _5.1.6_ new hello_app

・実装したい機能に応じてgemfileを修正し、必要なgemをインストール。
※gem:ある機能のソースコードの塊。機能を実装するたびに一からコードを書くのは大変なので、よく使う機能のコードはあらかじめまとまっている。
※gemfile:「このgemは、今このバージョンになっているよ!」といった感じで、gem毎のバージョンが書いてある場所。

filename.rb
$ bundle install

・アプリケーションとしてデータの出し入れが可能になるよう、仮置きのサーバーを起動。サーバーは開きっぱなしにすることが多いので、サーバー起動を指示する命令は、別ターミナルで打ち込んだ方が良い。

filename.rb
$ rails server

http://localhost:3000/をブラウザで開き、「Yay! You’re on Rails!]
と表示されたらOK。

・このままでは「hello, world!」と表示しないため、少し修正。
・applicationコントローラーで「helloアクション」を定義し、このアクションが呼ばれたら「hello, world」と表示されるよう設定。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  def hello
    render html: "hello, world!"
  end
end

・ブラウザで末尾が「/」のURLがリクエストがされたら、applicationコントローラの「helloアクション」を呼び出すよう設定。

config/routes.rb
Rails.application.routes.draw do
  root 'application#hello'
end

ここまで終えて、再度http://localhost:3000/をブラウザで開く(末尾が「/」のURLをリクエストする)と、「Hello world!」が表示されます。

あとは、ここまでのプログラムを本番環境にアップするだけです。
(以降はチュートリアルの手順通りに粛々と進めるだけなので割愛)

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