- 投稿日:2020-07-22T23:37:35+09:00
rubyでドローポーカーを作ってみる~実装編1(カード)~
概要
rubyでドローポーカーを作ってみる~準備編~
↓
rubyでドローポーカーを作ってみる~test-unit準備編~に続いて。
ソース: https://github.com/rytkmt/ruby_poker
今回からようやく実装に入っていこうと思います。
カードの実装
ポーカーで一番単一で動く末端はカードかなと思い、カードから作っていきます。
情報を整理していきましょう。
- 数字
- スート(記号)
をまず全カードが保持しているのでこれを実装します。
生成
- 全カードを生成するため、その際は数字とスートを必ず渡すため
initialize
の引数とする- 外部から数字とスートを参照できるが、変更はできないようにする
ruby_poker/card.rbmodule RubyPoker class Card attr_reader :suit, :number def initialize(suit:, number:) @suit = suit @number = number end end endそして新しいファイルのため読込
ruby_poker.rbrequire "ruby_poker/card"これで終わりかと思いきや、次にカードを使う際のことを考えてみます。
比較
カードは数字とスートを使い「役」を作り、役が判定となるため、カードは参照できれば十分?とも思ったのですが、
詳しく役の説明を確認すると、同一の役の場合に役のなかで使われたカードの強弱をもとに判定する といったカード同士の強弱があるとのこと。例えば「ツーカード」同士の場合
- 数字が大きいほうが勝ち
- 数字が同じ場合、スートの強いほうが勝ち準備編で確認したとおり下記の順番となる。
- 2人のプレイヤーが同じ役を作った場合は、役を構成しているカードの強い方が勝ちとなる
- カードの強い順位は、A・K・Q・J・10~2
- スートの強い順位は、スペード・ハート・ダイヤ・クローバー
配列の要素番号で判定できそうなので、定義する(定義は強い順に並んでいるのでindexを取る場合は逆順にする)
ruby_poker.rbmodule RubyPoker NUMBERS = ([1] + [*2..13].reverse).freeze SUITS = %i[spade heart diamond club].freeze endまたせっかくなので、
card.rb
側で引数チェックにも使用するruby_poker/card.rbdef initialize(suit:, number:) + raise(ArgumentError) unless RubyPoker::SUITS.include?(suit) + raise(ArgumentError) unless RubyPoker::NUMBERS.include?(number)
また、要素の強弱を判定するユースケースですが、下記が考えられました。
- 自分の役に使われた手札の中で、一番強いカード がどれか(ストレート同士の比較の場合など)
- 敵の役との比較の際に、一番強いカード同士での比較
1に関しては、複数の中から一番強いものを取りたいため
max
2に関しては大小の比較>
などそのため
Comparable
を使用し<=>
を実装すれば解決(max
は<=>
で判定されている Array#max )数字は単なる数字で比較してしまうと
1
が最弱になってしまうため、ちゃんとindex
をもとに比較するruby_poker/card.rbmodule RubyPoker class Card + include Comparable attr_reader :suit, :numberruby_poker/card.rbdef suit_level RubyPoker::SUITS.reverse.index(@suit) end def number_level RubyPoker::NUMBERS.reverse.index(@number) end def <=>(other) number_comparision = number_level <=> other.number_level number_comparision.zero? ? suit_level <=> other.suit_level : number_comparision endテストケース
簡単にテストケースも実装する ※test-unit初めてなので記述でいい方法あれば教えてください
require "test_helper" module RubyPoker class CardTest < Test::Unit::TestCase sub_test_case "#initialize" do test "correct arguments" do assert_nothing_raised do Card.new(suit: :heart, number: 3) end end test "wrong suit" do assert_raise_kind_of(ArgumentError) do Card.new(suit: :test, number: 3) end end test "wrong number" do assert_raise_kind_of(ArgumentError) do Card.new(suite: :heart, number: 14) end end end sub_test_case "#<=>" do sub_test_case "compare number" do test "simple numbers" do a = Card.new(suit: :heart, number: 8) b = Card.new(suit: :heart, number: 2) assert(a > b) end test "compare ace" do a = Card.new(suit: :heart, number: 13) b = Card.new(suit: :heart, number: 1) assert(a < b) end test "max number" do cards = [*1..5] .shuffle .map { |i| Card.new(suit: :heart, number: i) } assert_equal(1, cards.max.number) end end sub_test_case "compare suit(same number)" do test "spade and heart" do a = Card.new(suit: :spade, number: 1) b = Card.new(suit: :heart, number: 1) assert(a > b) end test "heart and club" do a = Card.new(suit: :club, number: 1) b = Card.new(suit: :heart, number: 1) assert(a < b) end test "spade and diamond" do a = Card.new(suit: :spade, number: 1) b = Card.new(suit: :diamond, number: 1) assert(a > b) end end end end endこれでひとまずカードに関しては完成 かな?
続き
- 投稿日:2020-07-22T23:14:08+09:00
[Ruby]puts とreturn、出力と戻り値の違い
putsは出力するのみ。対してreturnは戻り値を返すだけ。
putsは値の受け渡しに使えない。returnは出力をしない。puts.rbdef greet puts "Hello" end @example = greet @example =>nilreturn.rbdef greet2 return "Hello" end @example2 = greet2 @example =>"Hello"値の受け渡しをするのかどうかという点が使い分けのミソかな?
戻り値について勉強が足りないなあ
- 投稿日:2020-07-22T22:45:29+09:00
overflowプロパティ
overflowプロパティについて覚えたので備忘録
値は主に4つ
1. [visible] ボックスからはみ出した要素がそのまま表示される、初期値
2. [hidden] ボックスからはみ出した要素は表示されない
3. [scroll] ボックスからはみ出した要素は表示されないがスクロールすることで表示される
4. [auto] はみ出した要素の処理がブラウザに依存する、基本的にはスクロールすることで表示される補足として
1. overflow-x: scroll;
x軸(横方向)にのみスクロールさせる
2. overflow-y: scroll;
y軸(縦方向)にのみスクロールさせる文字列が横に長いが折り返しさせたくない + 項目がボックス内に収まらない
上記のような場合に使用検討できる
- 投稿日:2020-07-22T22:45:29+09:00
overflowプロパティ【残54日】
overflowプロパティについて覚えたので備忘録
値は主に4つ
1. [visible] ボックスからはみ出した要素がそのまま表示される、初期値
2. [hidden] ボックスからはみ出した要素は表示されない
3. [scroll] ボックスからはみ出した要素は表示されないがスクロールすることで表示される
4. [auto] はみ出した要素の処理がブラウザに依存する、基本的にはスクロールすることで表示される補足として
1. overflow-x: scroll;
x軸(横方向)にのみスクロールさせる
2. overflow-y: scroll;
y軸(縦方向)にのみスクロールさせる文字列が横に長いが折り返しさせたくない + 項目がボックス内に収まらない
上記のような場合に使用検討できる
- 投稿日:2020-07-22T22:40:39+09:00
【ポートフォリオを作成する方へ】chartkickの使い方
chartkickはとても簡単にグラフを作ることができるライブラリです
環境
Ruby 2.5.3
Ruby on Rails 5.2.4
chartkick 3.3.1手順
1.インストール
Gemfilegem "chartkick"$ bundle install2.javascript読み込み
app/javascripts/application.js//= require chartkick //= require Chart.bundle準備完了です。
一度サーバー落としてrails sやり直した方がいいかもしれません。オプション
ID,幅,高さ
<%= line_chart data, id: "users-chart", width: "800px", height: "500px" %>軸のタイトル
<%= line_chart data, xtitle: "Time", ytitle: "Population" %>これらを組み合わせるとこのような感じになります。
managemantに売上(result),売上日(result_date)を用意し、
app/controllers/managemants_controller.rbdef index @managemants = Managemant.all endapp/models/managemant.rb# chartkick用データ def self.chart_date order(result_date: :asc).pluck('result_date', 'result').to_h endapp/views/managemants/index.html.erb<%= column_chart @managemants.chart_date, xtitle: "日付", ytitle: "売上(円)", width: "600px", height: "200px" %>ビューのcolumn_chartの部分を変えるだけで色々なグラフに変えることができます。
・折れ線グラフ-line_chart
・円グラフ-pie_chart
・棒グラフ-pie_chart
簡単にきれいなグラフが作れるのでぜひ試してみてください。
参考
- 投稿日:2020-07-22T21:37:42+09:00
rubyでドローポーカーを作ってみる~test-unit準備編~
概要
に続いて。
ソース: https://github.com/rytkmt/ruby_poker
test-unit全然知らない・・・ということで今回はtest-unitの準備をしていきます。
準備
こちらの公式ページを参考に進めていく
が、gemではないので少し手順が変わりそう・・・
なのでそれも含めて手順を記録として残していきます初期ファイルの準備
$ cd ruby_poker $ bundle gem -t minitest ./git status -sb A .gitignore A .travis.yml A CODE_OF_CONDUCT.md A Gemfile A LICENSE.txt A Rakefile A bin/console A bin/setup A lib/ruby_poker.rb A lib/ruby_poker/version.rb A ruby_poker.gemspec A test/ruby_poker_test.rb A test/test_helper.rb結構作られる
gemではないためgemspec削除
$ git rm ruby_poker.gemspecファイルの修正
Gemfile
Gemfile-# Specify your gem's dependencies in ruby_poker.gemspec -gemspec +gem "rake" +group :test do + gem "pry-byebug" + gem "test-unit" + gem "test-unit-rr" +endgemspecの行削除
代わりにminitestからtest-unitに変更なのでまずgemをインストール$ bundle installこのとき、bundlerにてインストールされたファイルがgitに上がらないように
.gitignore
を必要に応じて編集Rakefile
Rakefile-require "bundler/gem_tasks" require "rake/testtask"
test/test_helper.rb
test/test_helper.rb$LOAD_PATH.unshift File.expand_path("../lib", __dir__) require "ruby_poker" -require "minitest/autorun" +require "pry" +require "test/unit" +require "test/unit/rr"
test/ruby_poker_test.rb
test/ruby_poker_test.rbrequire "test_helper" -class RubyPokerTest < Minitest::Test +class RubyPokerTest < Test::Unit::TestCase def test_that_it_has_a_version_number refute_nil ::RubyPoker::VERSION end
bin/console
bundle exec console
を実行したときにruby_pokerはLOAD_PATHに居ないためエラーになる。そのためLOAD_PATHに追加するbin/consolerequire "bundler/setup" +$LOAD_PATH.unshift File.expand_path("../lib", __dir__) + require "ruby_poker"
動作確認
console
bundle exec console
を実行し、irbが立ち上がればOKtest
bundle exec rake test
を実行し1件は失敗するようにテストケースがサンプルとして作られてるため、1件成功、1件失敗となればOK$ rake test /home/vagrant/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/rake-12.3.2/lib/rake/file_utils.rb:54: warning: Insecure world writable dir /home in PATH, mode 040707 Loaded suite /home/vagrant/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/rake-12.3.2/lib/rake/rake_test_loader Started F ============================================================================================================================================================== 6: end 7: 8: def test_it_does_something_useful => 9: assert false 10: end 11: end /home/vagrant/private_workspace/ruby_poker/test/ruby_poker_test.rb:9:in `test_it_does_something_useful' Failure: test_it_does_something_useful(RubyPokerTest): <false> is not true. ============================================================================================================================================================== . Finished in 0.004560792 seconds. -------------------------------------------------------------------------------------------------------------------------------------------------------------- 2 tests, 2 assertions, 1 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 50% passed -------------------------------------------------------------------------------------------------------------------------------------------------------------- 438.52 tests/s, 438.52 assertions/s rake aborted! Command failed with status (1) Tasks: TOP => test (See full trace by running task with --trace)続き
- 投稿日:2020-07-22T21:27:21+09:00
[Rails]Administrateでメインアプリで使っていたCSSを適用させる方法
困ったこと、やりたいこと
\\\メインアプリのCSSが使えない///
Railsアプリで管理画面を作成するために、Administrateというgemを使用した時のことです。
メインアプリのビューに適用させているCSSを、admin以下のビューにも適用させようとしたのにできない…メインアプリと同じクラス名使ってるのに…どうすればいいの???
環境
- Ruby 2.5.1
- Rails 5.0.7.2
- Administrate 0.14.0
- haml 5.1.2
結論
Administrateのスタイルシートの読み込み先を追加する
1.
rails g administrate:views:layout
で_styoesheet.html.erb
を生成terminal# 該当のアプリディレクトリで実行 $ rails g administrate:views:layout Running via Spring preloader in process 44282 create app/views/layouts/admin/application.html.erb create app/views/admin/application/_navigation.html.erb create app/views/admin/application/_stylesheet.html.erb #この子!!! create app/views/admin/application/_javascript.html.erb create app/views/admin/application/_flashes.html.erb create app/views/admin/application/_icons.html.erb※生成されるファイルはerbなので、環境がhamlの場合は
$ rails haml:erb2haml
などで変換してください。2.
_stylesheet.html.haml
にスタイルシートの読み込み先を追記する/views/admin/application/_stylesheet.html.haml- Administrate::Engine.stylesheets.each do |css_path| = stylesheet_link_tag css_path # この行↓を追記 = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' = yield :stylesheetこの記述によって、
/assets/stylesheets/application.scss
のファイルを読み込みにいってくれるので、結果的にadministrateの画面でも既存のスタイルシートを適用することができます。参考記事
【Rails5】Administrateのスタイルシート読込先を変更する - 196Log
余談:既存レイアウトのカスタマイズ
あまり需要もないかとは思いますが、既定のadministrate画面のレイアウトを変更(カスタマイズ)することも可能です。
こちらもGitHubのIssues(How to add custom CSS? #748)に挙げられていました。
terminal# 該当のアプリディレクトリで実行 $ rails g administrate:assets:stylesheets Running via Spring preloader in process 44122 create app/assets/stylesheets/administrate create app/assets/stylesheets/administrate/application.scss create app/assets/stylesheets/administrate/base/_forms.scss create app/assets/stylesheets/administrate/base/_layout.scss create app/assets/stylesheets/administrate/base/_lists.scss create app/assets/stylesheets/administrate/base/_tables.scss create app/assets/stylesheets/administrate/base/_typography.scss create app/assets/stylesheets/administrate/components/_app-container.scss create app/assets/stylesheets/administrate/components/_attributes.scss create app/assets/stylesheets/administrate/components/_buttons.scss create app/assets/stylesheets/administrate/components/_cells.scss create app/assets/stylesheets/administrate/components/_field-unit.scss create app/assets/stylesheets/administrate/components/_flashes.scss create app/assets/stylesheets/administrate/components/_form-actions.scss create app/assets/stylesheets/administrate/components/_main-content.scss create app/assets/stylesheets/administrate/components/_navigation.scss create app/assets/stylesheets/administrate/components/_pagination.scss create app/assets/stylesheets/administrate/components/_search.scss create app/assets/stylesheets/administrate/library/_clearfix.scss create app/assets/stylesheets/administrate/library/_data-label.scss create app/assets/stylesheets/administrate/library/_variables.scss create app/assets/stylesheets/administrate/reset/_normalize.scss create app/assets/stylesheets/administrate/utilities/_text-color.scssこのように、スタイルシートがブワーッと生成されるので、あとは自分がカスタマイズしたい部分をいじれば変更が適用されます。
それにしてもAdministrateは都度都度こういうのがあるな…一通り実装終えたらシリーズ化できそうです笑
- 投稿日:2020-07-22T21:12:22+09:00
プログラミング未経験者が3ヶ月でポートフォリオを作成するまで
はじめに
プログラミング完全未経験者だった私がポートフォリオを作成するまでの3ヶ月で取り組んだことをまとめました。
ポートフォリオは基礎学習(1ヶ月)ポートフォリオ作成(2ヶ月)の計3ヶ月で作成しました。
私と同じように未経験からエンジニアを目指す方の参考になれば嬉しいです。作成したポートフォリオ
【概要】
ゴルフ初心者向けの投稿アプリです。
学生時代の経験からゴルフ初心者が持つ課題を解決するためのツールとして作成しました。
【URL】
https://golfour.herokuapp.com
【GitHub】
https://github.com/matao0214/golfour
【言語・使用技術】
・Ruby 2.5.1
・Ruby on Rails 5.2.4
・HTML(Slim)
・CSS(Sass)
・Bootstrap4
・JavaScript
・jQuery
・PostgreSQL 12.2
・GoogleMapsAPI
・Heroku
・Git
・GitHub基礎学習(1ヶ月)
まずは基礎的な知識をインプットするために以下の書籍を使って学習を進めました。
これからWebをはじめる人のHTML&CSS、JavaScriptのきほんのきほん
プロを目指す人のためのRuby入門
現場で使える Ruby on Rails 5速習実践ガイドこの1ヶ月はかなり辛かったですが以下の2点を習慣化して取り組みました。
1. 知らない単語が出なくなるまでググり続ける
わからないことを調べるとその説明文の中に知らない単語が出てきて更に調べて、というのを何度も繰り返しました。
ただ、それを続けていくと確実に知識量が増えて調べる量は減っていくので、諦めずに調べ続けることが重要です。2. インプットした内容はQiitaでアウトプットする
インプットした知識は理解度を高めるために、Qiitaの記事にまとめてアウトプットしていました。
本を読んで「理解した!」と思ってもいざアウトプットしようとすると言語化出来ず、十分に理解できていないことが多々ありました。
理解度の確認・定着のためにもQiitaへのアウトプットは非常に有効だと思うのでおすすめです。わかりやすい文章を書こう!とは考えずにとりあえず書いてみるのが重要です!
数をこなしていけば書き慣れていくので積極的にアウトプットして習慣化するのが良いと思います。ポートフォリオ作成(2ヶ月)
基礎学習が一通り終わったところでポートフォリオ作成に移りました。
ポートフォリオは以下の手順で作成しました。
1. ポートフォリオのアイデア出し
2. 機能・DBの設計
3. ポートフォリオの作成1. ポートフォリオのアイデア出し
ポートフォリオはどういうコンセプト・背景があってポートフォリオを作成したのか?という点を意識して設計しました。
これまでに自分または身近な人が抱えている(抱えていた)問題を書き出してみて、
その問題はどういう機能・仕組みがあれば解決できるかを考えポートフォリオに落とし込みました。具体的には以下の順序でアイデアを出して形にしていきました。
私は大学生の時、研究室に入るのをきっかけにゴルフを始めた。そしてこれまでにゴルフに関して印象に残った2つの経験があった。
- 私が以前働いていた会社でゴルフを始めた先輩が2人いた。2人ともなかなか上達せず途中で辞めそうになっていたが、アドバイスしあいながら練習を続けた結果、徐々に上達し今でもゴルフを楽しんで続けている。
- 大学の同期に「ゴルフを始めたいが一緒に練習に行ってくれる同年代(20代)の人がなかなかいない」という人が結構いた。
この経験からゴルフ初心者には次の課題があると感じた。
・ゴルフ初心者はなかなか上達が難しく、途中で諦めてしまうことが多い。
・ゴルフを始めたい20代は一定数いるが、一緒に始める人が身近にいない。
↓
課題の解決策:同じ境遇(ゴルフを始めたい・ゴルフを始めたばかり)の若い世代の人が身近にいれば、ゴルフを始めやすい環境、挫折しにくい仕組みが作れるのでは?
↓
サービスの形:ゴルフの練習記録を投稿して共有するアプリケーション2. 機能・DBの設計
ポートフォリオのアイデアが固まったところで機能・DBの設計を行いました。
ゴルフの練習記録を投稿して共有する機能を中心に、練習のモチベーションを維持できる機能を併せて設計をしました。機能設計
新規投稿機能
一覧表示機能
詳細表示機能
編集機能
削除機能
ログイン機能
検索機能
いいね機能
位置情報投稿機能(GoogleMapAPIの利用)
投稿データからグラフ作成機能DB設計
Usersテーブル
カラム名 データ型 制約 nickname string NOT NULL,10字以内 string NOT NULL,UNIQUE password_digest string NOT NULL golf_reki string 5字以内 goal string 50字以内 TrainingPostsテーブル
カラム名 データ型 制約 training_place string NOT NULL, 50字以内 training_task string NOT NULL, 150字以内 training_impression string 150字以内 user_id integer NOT NULL, FOREIGN KEY TrainingContentsテーブル
カラム名 データ型 制約 training_post_id integer NOT NULL,Foreign_key training_time integer NOT NULL training_hits integer NOT NULL Likesテーブル
カラム名 データ型 制約 user_id integer NOT NULL, FOREIGN KEY training_post_id integer NOT NULL, FOREIGN KEY Spotsテーブル
カラム名 データ型 制約 address integer NOT NULL, 50字以内 latitude float NOT NULL longitude float NOT NULL training_post_id integer NOT NULL, FOREIGN KEY 3. ポートフォリオ作成
機能・DB設計の終わりようやくコードを書く作業に入りました。
ポートフォリオの作成手順は以下の通りです。
1. 基礎学習で使った書籍をベースにCRUD機能を実装
2. 実装したい機能をQiitaの記事・公式ドキュメントなどを参考に実装
3. UI/UXを意識したデザイン1. 基礎学習で使った書籍をベースにCRUD機能を実装
基礎学習を終えた直後ではまだまだコーディングに慣れていないので「この機能を実装しよう!」と思っても
・どうやって実装する?
・何がわからないのかわからない
という状態だったのでコードに慣れる意味合いも込めて書籍を参考にしました。結果的に基礎的な知識の復習とプログラム全体の構造に慣れることができました。
また、自分でもここまでできるんだ!と自信を持てるので
モチベーションを維持するためにも難易度の低いことから手をつけるのが良いと思います。2. 実装したい機能をQiitaの記事・公式ドキュメントなどを参考に実装
CRUD機能を実装した後はいいね機能の実装やGoogle MAP APIの利用等に取り組みました。
機能の実装ではググりながらQiitaや公式ドキュメントを参考に実装したのですが、
調査する上で重要だと感じたのは絶対に公式ドキュメントを読むことです。公式ドキュメントを読むと実装したい機能の説明の他にこんなオプションもあるよ!と書いてあることが多いです。
そういったオプションなどを取り入れることで機能が予定していた以上に充実したことが何度もありました。Qiitaの記事はわかりやすくまとめられていて非常に読みやすく便利です。
ただ、当たり前ですがソースの信頼性が公式ドキュメントに比べると低く、必要以上の情報に触れられないので、Qiitaの記事だけではなく公式ドキュメントも読むべきですね。3. UI/UXを意識したデザイン
一通り機能の実装が終わった後、UI/UXを意識してデザインに取り掛かりました。
UI・UX改善入門:「UIとUXの違い」から「UX改善の3ポイント」を大解説!具体的には以下の3点を意識しました。
・ターゲット(20代)を意識したデザイン
・使い続けたくなるデザイン
・直感的なデザインそしてポートフォリオには以下の形で落とし込みました。
・若者向けのポップな背景・フォント
・投稿内容の入力欄を具体的な選択肢からの選択式に変更(入力内容を考えさせる手間を省略)
・グラフを利用して直感的に練習記録を可視化この中でも特にグラフによる可視化は取り入れて良かったです。
最初はユーザーの練習記録を文字と数字で表示していましたが、データをグラフ化することで欲しい情報が視覚的に得られやすいデザインを実現できました。
chartkickというjemを使えば簡単にグラフを作成できるのでおすすめです。ポートフォリオ作成で意識したこと・工夫した点
・とにかくできることから手を動かしてコードに慣れる
・使い続けたくなる機能の実装・仕組みの構築
・ターゲットを意識したデザイン
・ユーザー目線での使いやすさ
・保守性の高いコーディングポートフォリオ作成を通して学んだこと・成長したこと
・自分のアイデアが形になる楽しさ(一番実感したことです)
・サービスを1から作れるエンジニアとしての自走力
・わからないことを言語化して調査する能力(ググる力)
・参考文献の選び方(公式ドキュメントは必須)
・アウトプットの重要性まとめ
ポートフォリオ作成を通してこの3ヶ月間で多くのことを学び成長することができました。
全くプログラミングを触ったことがない自分でもここまで作れるんだという自信が得られたこと、そして何より自分のアイデアでサービスを作る楽しさを知ることができました。AWSによるインフラ構築などまだまだやりたいこともありますが、まずはエンジニアへの就職活動を始めます。
ポートフォリオをアップデートしながら就活をがんばっていきたいと思います!
- 投稿日:2020-07-22T19:45:22+09:00
find_in_batchesでメモリ消費量を1/100にしたお話
メモリ消費を抑えた大量データ取り扱い方法について
Railsでメモリを意識してコードを書いていますでしょうか?
RailsではRubyのガベージコレクション※1を利用しているため、メモリ解放などを気にすることなくコードを書くことが出来ます。
※1 Rubyは使用されなくなったオブジェクトを回収し、自動的にメモリを解放します。その為、いつの間にかメモリをクソほど食う実装をしているにも関わらず、その事に気づかずに本番サーバーが急に落ちる(メモリエラーで)といった非常事態になるケースがあります。
なぜそんなことが言えるかというと、僕が今働いている現場でこの現象が起きたからですw
実装修正の担当が自分になり修正したのですが、その経験で多くを学ぶことが出来たので忘れないためにもメモ残します。
原因調査
まずはそもそもどこで、メモリエラーになっているのかを調査しなければなりません。
Railsでメモリ使用量の調査をする為に
ObjectSpace.memsize_of_all
を利用しました。このメソッドを利用することで、すべての生存しているオブジェクトが消費しているメモリ使用量をバイト単位で調査することが出来ます。
このメソッドを実行処理が落ちそうな箇所にチェックポイントとして設置し、どこでメモリを大量消費しているのかを地道に調べていきます。
■ メモリ使用量を調べる使用例
class Hoge def self.hoge puts 'mapでメモリ展開される前のオブジェクトメモリ数' puts '↓' puts ObjectSpace.memsize_of_all <==== チェックポイント array = ('a'..'z').to_a array.map do |item| <==== ① puts "#{item}のオブジェクトメモリ数" puts '↓' puts ObjectSpace.memsize_of_all <==== チェックポイント item.upcase end end end■ 実行結果
irb(main):001:0> Hoge.hoge mapでメモリ展開される前のオブジェクトメモリ数 ↓ 137789340561 aのオブジェクトメモリ数 ↓ 137789342473 bのオブジェクトメモリ数 ↓ 137789342761 cのオブジェクトメモリ数 ↓ 137789343049 dのオブジェクトメモリ数 ↓ 137789343337 eのオブジェクトメモリ数 ↓ 137789343625 . . . xのオブジェクトメモリ数 ↓ 137789349097 yのオブジェクトメモリ数 ↓ 137789349385 zのオブジェクトメモリ数 ↓ 137789349673 => ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]この実行結果からまずmapで渡されるデータを一気にメモリ展開しており、そこでメモリ消費量が増えたことがわかります。(①の箇所)
さらにループ処理される度にメモリ消費量が増えていることもわかります。
今回のサンプルコードのように処理が単純である場合は特に問題ありませんが。
渡されるデータが大量であり、なおかつループ処理で行う実装が複雑であればメモリ消費量が圧迫されて。
メモリーエラー(メモリ処理が追いつかなくなった場合に起こるエラー)になってしまいます。
今回の調査も以上の手順で調べました、その結果、渡されるデータが大量であり尚且つmapでクエリを吐きまくる重い処理を実装していたのでメモリエラーになったという結論になりました。
対策方法
原因はわかりました。
次に対策方法について考えましょう。
最初に考えついた対策は以下の3つです。
1. 金の力でメモリ増やす 2. Thread(スレッド)で並行処理にする 3. バッチ処理にする1. 金の力でメモリ増やす
正直これが一番早い、お金の力でサーバーのメモリスペックを上げれば済むことなのでこれにしよう!
っと、思ったのですが。
この処理以外にメモリ負担の大きい実装はないので、この箇所のためだけにお金をかけるのは馬鹿らしいなと思いこの案はやめました。
2. Thread(スレッド)で並行処理にする
次の対策方法としてRubyの並行処理を考えついたのですが、ボトルネックが処理時間(timeout)の場合、複数スレッドを立てて並列して計算させてマージすると早くなるので正しいのですが、今回はメモリエラーでボトルネックがメモリ圧迫なので、複数スレッドにしようが扱うデータ量は変わらないので結局メモリエラーになると想定されるので、この案はやめました。
3. バッチ処理にする
今回のメモリエラーになる最大の原因は大量のデーターを一気にメモリ展開して、ループで高負荷処理を繰り返すことが原因で発生するメモリエラーです。
なので、大量データを一気にメモリ展開せずにバッチ処理で1,000件単位などに分けて実装すればメモリを節約しながら実装できるので良いのではないかと考えました。
Railsでは
find_in_batches
というメソッドが準備されており、こちらを利用するとデフォルトで1000件ずつ処理を実施することができます。例)10,000件の場合は1,000件の処理に分けて10回のバッチ処理に分ける。 find_in_batchesで制限をかけて処理することで利用するメモリを少なくするイメージ。結論
find_in_batchesを利用してバッチ処理にする
実装
対策方法がわかれば、あとは実装あるのみです。
実装していきましょう。(実際に会社のコードを見せる事はできないのでイメージだけ記載します)
■ 実装イメージ
User.find_in_batches(batch_size: 1000) do |users| # なんか処理 endもし仮にUserデータが10,000件取得されたとしても、find_in_batchesを利用すれば1000ずつ処理されます。
つまり、10,000 / 1000 = 10回の処理に分けるイメージです。
結果
メモリ消費量が1/100になりました。
もっと良くするためのアイデア
ただ、この実装の最大のデメリットは、処理時間がかかりすぎるポイントです。
herokuなどを利用している場合はこの実装ではRequestTimeOutエラー※1になります。
※1 herokuでは30秒以上かかる処理はRequestTimeOutエラーになる仕様ですので、この高負荷処理がかかる実装はバックグランド処理に移動させるのがベターと考えます。
Railsを利用している場合はSidekiqなどを利用すれば実現可能です。
以下のような手順で作業すれば、良いと思います。
STEP1. find_in_batchesを利用してメモリ消費量を抑える STEP2. STEP1が完了した段階で、時間はかかるけどメモリエラーにならずに動く状態になってるはず。 ただ、処理に時間がかかるのでその処理をバックグランドに移動させるまとめ
最初は、めんどくさそうなタスクだな〜と思ってたのですが。
学びが多く、今思うと実装してよかったです。
参考
https://techblog.lclco.com/entry/2019/07/31/180000
https://qiita.com/kinushu/items/a2ec4078410284b9856d
- 投稿日:2020-07-22T19:45:22+09:00
find_in_batchesでメモリ消費量を1/100にしたお話(体験談)
メモリ消費を抑えた大量データ取り扱い方法について
Railsでメモリを意識してコードを書いていますでしょうか?
RailsではRubyのガベージコレクション※1を利用しているため、メモリ解放などを気にすることなくコードを書くことが出来ます。
※1 Rubyは使用されなくなったオブジェクトを回収し、自動的にメモリを解放します。その為、いつの間にかメモリをクソほど食う実装をしているにも関わらず、その事に気づかずに本番サーバーが急に落ちる(メモリエラーで)といった非常事態になるケースがあります。
なぜそんなことが言えるかというと、僕が今働いている現場でこの現象が起きたからですw
実装修正の担当が自分になり修正したのですが、その経験で多くを学ぶことが出来たので忘れないためにもメモ残します。
原因調査
まずはそもそもどこで、メモリエラーになっているのかを調査しなければなりません。
Railsでメモリ使用量の調査をする為に
ObjectSpace.memsize_of_all
を利用しました。このメソッドを利用することで、すべての生存しているオブジェクトが消費しているメモリ使用量をバイト単位で調査することが出来ます。
このメソッドを実行処理が落ちそうな箇所にチェックポイントとして設置し、どこでメモリを大量消費しているのかを地道に調べていきます。
■ メモリ使用量を調べる使用例
class Hoge def self.hoge puts 'mapでメモリ展開される前のオブジェクトメモリ数' puts '↓' puts ObjectSpace.memsize_of_all <==== チェックポイント array = ('a'..'z').to_a array.map do |item| <==== ① puts "#{item}のオブジェクトメモリ数" puts '↓' puts ObjectSpace.memsize_of_all <==== チェックポイント item.upcase end end end■ 実行結果
irb(main):001:0> Hoge.hoge mapでメモリ展開される前のオブジェクトメモリ数 ↓ 137789340561 aのオブジェクトメモリ数 ↓ 137789342473 bのオブジェクトメモリ数 ↓ 137789342761 cのオブジェクトメモリ数 ↓ 137789343049 dのオブジェクトメモリ数 ↓ 137789343337 eのオブジェクトメモリ数 ↓ 137789343625 . . . xのオブジェクトメモリ数 ↓ 137789349097 yのオブジェクトメモリ数 ↓ 137789349385 zのオブジェクトメモリ数 ↓ 137789349673 => ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]この実行結果からまずmapで渡されるデータを一気にメモリ展開しており、そこでメモリ消費量が増えたことがわかります。(①の箇所)
さらにループ処理される度にメモリ消費量が増えていることもわかります。
今回のサンプルコードのように処理が単純である場合は特に問題ありませんが。
渡されるデータが大量であり、なおかつループ処理で行う実装が複雑であればメモリ消費量が圧迫されて。
メモリーエラー(メモリ処理が追いつかなくなった場合に起こるエラー)になってしまいます。
今回の調査も以上の手順で調べました、その結果、渡されるデータが大量であり尚且つmapでクエリを吐きまくる重い処理を実装していたのでメモリエラーになったという結論になりました。
対策方法
原因はわかりました。
次に対策方法について考えましょう。
最初に考えついた対策は以下の3つです。
1. 金の力でメモリ増やす 2. Thread(スレッド)で並行処理にする 3. バッチ処理にする1. 金の力でメモリ増やす
正直これが一番早い、お金の力でサーバーのメモリスペックを上げれば済むことなのでこれにしよう!
っと、思ったのですが。
この処理以外にメモリ負担の大きい実装はないので、この箇所のためだけにお金をかけるのは馬鹿らしいなと思いこの案はやめました。
2. Thread(スレッド)で並行処理にする
次の対策方法としてRubyの並行処理を考えついたのですが、ボトルネックが処理時間(timeout)の場合、複数スレッドを立てて並列して計算させてマージすると早くなるので正しいのですが、今回はメモリエラーでボトルネックがメモリ圧迫なので、複数スレッドにしようが扱うデータ量は変わらないので結局メモリエラーになると想定されるので、この案はやめました。
3. バッチ処理にする
今回のメモリエラーになる最大の原因は大量のデーターを一気にメモリ展開して、ループで高負荷処理を繰り返すことが原因で発生するメモリエラーです。
なので、大量データを一気にメモリ展開せずにバッチ処理で1,000件単位などに分けて実装すればメモリを節約しながら実装できるので良いのではないかと考えました。
Railsでは
find_in_batches
というメソッドが準備されており、こちらを利用するとデフォルトで1000件ずつ処理を実施することができます。例)10,000件の場合は1,000件の処理に分けて10回のバッチ処理に分ける。 find_in_batchesで制限をかけて処理することで利用するメモリを少なくするイメージ。結論
find_in_batchesを利用してバッチ処理にする
実装
対策方法がわかれば、あとは実装あるのみです。
実装していきましょう。(実際に会社のコードを見せる事はできないのでイメージだけ記載します)
■ 実装イメージ
User.find_in_batches(batch_size: 1000) do |users| # なんか処理 endもし仮にUserデータが10,000件取得されたとしても、find_in_batchesを利用すれば1000ずつ処理されます。
つまり、10,000 / 1000 = 10回の処理に分けるイメージです。
結果
メモリ消費量が1/100になりました。
もっと良くするためのアイデア
ただ、この実装の最大のデメリットは、処理時間がかかりすぎるポイントです。
herokuなどを利用している場合はこの実装ではRequestTimeOutエラー※1になります。
※1 herokuでは30秒以上かかる処理はRequestTimeOutエラーになる仕様ですので、この高負荷処理がかかる実装はバックグランド処理に移動させるのがベターと考えます。
Railsを利用している場合はSidekiqなどを利用すれば実現可能です。
以下のような手順で作業すれば、良いと思います。
STEP1. find_in_batchesを利用してメモリ消費量を抑える STEP2. STEP1が完了した段階で、時間はかかるけどメモリエラーにならずに動く状態になってるはず。 ただ、処理に時間がかかるのでその処理をバックグランドに移動させるまとめ
最初は、めんどくさそうなタスクだな〜と思ってたのですが。
学びが多く、今思うと実装してよかったです。
参考
https://techblog.lclco.com/entry/2019/07/31/180000
https://qiita.com/kinushu/items/a2ec4078410284b9856d
- 投稿日:2020-07-22T19:20:21+09:00
Yay! I'm on Rails! Again
はじめに
Ruby on Rails
三回目記念です。on Rails!
環境
前回、Yay! I'm on Rails! は
raspberry pi 3 B+
で、少しの嵌りで乗り切りました。
しかし、次のraspberry pi zero
は嵌りの連続で、いまだrails s
が成功していない状態です。
- sudo apt-get ruby-dev
- sudo gem install sassc
- sudo gem install sassc-rails
- sudo gem update sass-rails
- sudo gem install semantic-range
- sudo gem install webpacker
そこで、気分を変え
Windows10
でrails s
しました。嵌ったところ
嵌りらしい嵌りはないように感じました。
D:\ruby\rails>rails new test271 Done in 3983.70s. Webpacker successfully installed ? ?しかし、時間が掛かり過ぎの様な気もします。
まとめ
- 初RoR on Windows10
- 次回こそは外部サーバに挑戦
参照したサイト
Rails Girls インストール・レシピ
- 投稿日:2020-07-22T18:41:42+09:00
IGVをソケット通信をつかって操作する方法、および、その方法を利用してRubyのGemを作った話
はじめに
IGVはゲノムブラウザと呼ばれるソフトウェアです。アライメントされたリードのbamファイルや、ゲノム上の位置情報にアノテーションを加えたbedファイル、遺伝子情報の書かれたgff3ファイルなどさまざまなファイルを閲覧することができます。bioinformaticsに関わったことがある方は、このソフトをご存知の方が多いのではないでしょうか。
GithubのIGVのリポジトリ
※画像はIGVのホームページよりそんなIGVですが、マウスでクリックしながら操作すると少し手間がかかることがあります。
- リファレンスゲノムを指定して
- 必要なファイルを読み込んで
- 注目している場所を一つずつみてまわって
- スクリーンショットをとって画像として保存する
目視で結果を確認することは楽しいことでもあり、とても大事な作業だと思いますが、一方で定型的な作業はなるべく自動化していきたいという気持ちもあると思います。そこでIGVの操作をプログラミング言語から行うことを考えます。
※ 最近はJavaScriptで作られたigv.jsというものが活発に開発されて広く使われるようになっているようです。これに関しては調査が不十分なのでこの記事では触れません。(しかしいずれはQiita記事にしてきたいと思います。)
ソケット通信を利用してIGVを操作する
実はIGVはソケット通信を利用してport 60151番から操作できるようになっています。
メニューバーの View > Preference の中に advanced というタブがあります。Enable portにチェックが付いていない時は、ここをチェックします。
するとport 60151番を使って、ソケット通信を利用してIGVを操縦することができるようになります。
IGVの操作に使えるコマンドの一覧
公式リファレンスをDeepL翻訳を使いながら日本語に翻訳してみました。
Command Description new 新しいセッションを作成します。 デフォルトのゲノムアノテーション以外のすべてのトラックを取り除きます。 load file データまたはセッションファイルを読み込みます。フルパスまたはURLをカンマ区切りで指定します。 collapse trackName 指定されたtrackNameを折りたたむ。trackNameを指定しない場合はすべてのトラックが折りたたまれます。 echo レスポンスに "echo "を返します。 (テスト用) exit IGVアプリケーションを終了します。 expand trackName 指定したtrackNameを展開します。trackNameを指定しない場合はすべてのトラックが展開されます。 genome genomeIdOrPath idでゲノムを選択、または指定されたパスからゲノム(インデックス化されたfasta)をロードします。 goto locus or listOfLoci 単一の遺伝子座またはスペースで区切られた遺伝子座のリストにスクロールします。リストが提供されている場合は、これらの座位が分割スクリーンビューで表示されます。 IGV検索ボックスで有効な構文ならどれでも大丈夫です。 goto all ゲノム全体の表示にスクロールします。 group option アライメントトラックのみ。 次のオプションのいずれかでアライメントをグループ化します。 STRAND, SAMPLE, READ_GROUP, LIBRARY, FIRST_OF_PAIR_STRAND, TAG, PAIR_ORIENTATION, MATE_CHROMOSOME, SUPPLEMENTARY, MOVIE, ZMW, HAPLOTYPE, READ_ORDER, NONE, BASE_AT_POS region chr start end 2つの遺伝子座で囲まれた関心領域を定義します(例えば、region chr1 100 200)。 maxPanelHeight height 画像に含める各パネルの縦方向のピクセル数(高さ)を設定します。ポートコマンドまたはバッチスクリプトから作成された画像は、画面上に表示されるデータに限定されません。別の言い方をすれば、スクロール可能な画面領域に表示されている部分だけでなく、パネル全体を画像に含めることができます。この設定のデフォルト値は1000で、より多くのデータを表示するにはこの値を大きくし、より小さい画像を作成するにはこの値を小さくします。 setLogScale(true or false) setSleepInterval ms 遅延(スリープ)時間をミリ秒単位で設定します。 スリープ間隔は、連続するコマンド間に呼び出されます。 snapshotDirectory path 画像を書き込むディレクトリを設定します。 snapshot filename IGV ウィンドウのスナップショットを画像ファイルに保存します。 filenameを省略した場合、軌跡に基づいて生成されたファイル名を持つPNGファイルを書き込みます。 filenameが指定された場合、ファイル名の拡張子によって画像ファイルの形式が決定され、.png、.jpg、または.svgでなければなりません。 sort option locus アライメントまたはセグメント化されたコピーナンバーのトラックをソートします。 セグメント化されたコピーナンバーのoptionに適応される値は、(1)セグメント化されたコピー番号のAMPLIFICATION と DELETION、(2)アライメントトラックのPOSITION、STRAND、BASE、QUALITY、SAMPLE、READGROUP、INSERSTSIZE、FIRSTOFPAIRSTRAND、MATECHR、READORDER、およびREADNAMEです。 optionは、大文字と小文字を区別しません。 locusを指定すると、単一の位置または範囲を定義することができます。 オプションを指定しない場合は、表示されている領域、または表示されている領域の中心位置に基づいてソートが実行されます。 squish trackName 与えられた trackName を Squish します。 trackName はオプションで、指定しない場合はすべてのアノテーショントラックが Squish されます。 viewaspairs trackName アライメントトラックの表示モードを "View as pairs "に設定します。 preference key value keyという名前の設定を指定した値に一時的に設定します。この設定は、IGVがシャットダウンされるまでのみ有効です。 IGVをプログラミング言語から操る
Java
IGVはJavaで開発されているソフトウェアなので、公式のexampleもJavaで書かれています。
Socket socket = new Socket("127.0.0.1", 60151); PrintWriter out = new PrintWriter(socket.getOutputStream(), true); BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); out.println("load na12788.bam,n12788.tdf"); String response = in.readLine(); System.out.println(response); out.println("genome hg18"); response = in.readLine(); System.out.println(response); out.println("goto chr1:65,827,301"); //out.println("goto chr1:65,839,697"); response = in.readLine(); System.out.println(response); out.println("snapshotDirectory /screenshots"); response = in.readLine(); System.out.println(response); out.println("snapshot"); response = in.readLine(); System.out.println(response);R
さて、Java以外のプログラミング言語ではどうでしょうか。Rを使っている場合は、あまり詳しくないですが、bioconductorあたりからIGVを操作するライブラリが提供されているでしょうから、それを使えばよいでしょう。少し検索するだけでも igvRというソフトウェアがヒットします。これは igv.jsを活用するものらしいので、デスクトップのIGVを自動操作するものではないかも知れませんが…。
Python
少し古いスクリプトですが、最近精力的にNim言語を使用してバイオインフォマティックスのツールを開発されているBrent Pedersenさんが作成した igv.pyというツールがあります。これは上記のソケット通信をラップした小さなライブラリです。
Ruby
私はRubyが好きです。ナニナニ? ツールが少ない? それなら自分で作ってしまえばいいでしょうが!
ということで、上記のBrent Pedersenさんのスクリプトをまるっと参考にして、Ruby言語からIGVを操作できる ruby-igv というツールを作成しました。これでRuby言語からも簡単にIGVを操作できるようになります。https://github.com/kojix2/ruby-igv
使い方はこんな感じです。
igv = IGV.new igv.load 'na12788.bam' igv.genome 'hg18' igv.go 'goto chr1:65,827,301' igv.save 'image.png'まだ出来たてホヤホヤのツールです。
粗削りのところとか、バグとかに遭遇した場合、あるいは要望を見つけましたらぜひGithubのissueに報告してください。もちろんプルリクエストも歓迎します。おわりに
bioinformaticsに限らずですが、どうしても、どうすれば既存の道具を組み合わせて目的が達成できるか、どうすればツールを使いこなせるかという点に注目が集まりがちです。そして、せこせこと道具を作っていると、目的と手段が逆転しているなどという風にとらえられることもありえます。
でもそれは、ある意味では、とてもセルフィッシュな視野の狭い考え方だと思うんですよね。もっと道具を作って公開する人がいれば、それだけ世界は便利になって広がって豊かになっていきます。みんなもっと自由に道具を作って公開しよう。(別にRubyじゃなくてもいいので)
- 投稿日:2020-07-22T18:16:17+09:00
これで分かった!最短マッチ
はじめに
正規表現で最短マッチというのがあるが、いくつかサンプルで動作を見ながら、どういう使い方をするのか確認してみよう。
内容
str = "ああああ「あ」い「うう」ええ「おおお」かかか"
このような文字列の中から、括弧で囲まれた部分のみ抽出したい(つまり、「あ」「うう」「おおお」の3ヶ所を抽出したい)場合、最短マッチを利用する事になります。その前に、簡単な動きから確認してみよう。
+は、1文字以上でマッチするため、この例ではマッチしません。
str = "ああああ「」" puts str.scan(/「.+」/) =>マッチしない*は、0文字以上でマッチするため、この例ではマッチします。
str = "ああああ「」" puts str.scan(/「.*」/) =>「」次の場合はどうだろうか
あの文字が1文字あるためマッチします。
str = "ああああ「あ」" puts str.scan(/「.+」/) =>「あ」こちらも、あの文字が0文字あるためマッチします。結果的に同じです。
str = "ああああ「あ」" puts str.scan(/「.*」/) =>「あ」少し、本題に入ります
この例だと、最初に出現した、「と、最終に出現した、」で囲まれた部分でマッチしてしまいます。やりたい事はこれではありません。
str = "ああああ「あ」い「うう」ええ「おおお」かかか" puts str.scan(/「.+」/) =>「あ」い「うう」ええ「おおお」で、どうすればいいか
?を付加すれば、最短マッチをしてくれるようになります。
str = "ああああ「あ」い「うう」ええ「おおお」かかか" puts str.scan(/「.+?」/) =>「あ」「うう」「おおお」おめでとうございます。
こうすればどうなる?
+の代わりに、*にしても結果的には同じではありますが・・・
str = "ああああ「あ」い「うう」ええ「おおお」かかか" puts str.scan(/「.*?」/) =>「あ」「うう」「おおお」「」をマッチさせたいか、させたくないかで、+と*を使い分ける事になります。
「」をマッチさせたくない場合は、1文字以上でマッチする、+を使います。
str = "ああああ「あ」い「うう」ええ「おおお」かかか「」" puts str.scan(/「.+?」/) =>「あ」「うう」「おおお」「」をマッチさせたい場合は、0文字以上でマッチする、*を使います。
str = "ああああ「あ」い「うう」ええ「おおお」かかか「」" puts str.scan(/「.*?」/) =>「あ」「うう」「おおお」「」C'est fini
- 投稿日:2020-07-22T17:48:41+09:00
[Rails]deviseにカラムを追加する
なんで書いた
deviseでユーザー認証を作るとemailとpasswordは最初からあるけど、ユーザー登録するのに名前は必要でしょ。といつも思ってるのでメモっとく。
前提
- 既にdeviseのgem追加してること
rails g devise User
してあることカラムを追加する
DBにカラムを追加するときは、以下のような書き方をするのが一般的です。
とりあえず、そのマイグレーションファイルが何をするためのファイルがわかる名前にしましょう。名前つけるとき、
AddColumnToUsers
って書いて「ユーザーテーブルにカラム追加する」みたいな意味だと、次カラム追加する時名前困るので注意。そもそもカラムを追加することになる=DB設計ちゃんとしろってことなのかと思ったりしてますが、この辺はまだまだ知識が浅いので強い方教えて欲しいです。$ rails generate migration Addカラム名Toテーブル名 追加するカラム:データ型
$ rails generate migration AddNameToUsers name:string
XXXXXXXXXXX_add_name_to_users.rbclass AddNameToUsers < ActiveRecord::Migration[5.2] def change add_column :users, :name, :string end end$ rails db:migrate
ControllerとViewの修正
registrations/new.html.erb<h2>Sign up</h2> <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> <%= render "devise/shared/error_messages", resource: resource %> # ここから追加 <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name, autofocus: true %> </div> # ここまで <div class="field"> <%= f.label :email %><br /> <%= f.email_field :email, autofocus: true, autocomplete: "email" %> </div> <div class="field"> <%= f.label :password %> <% if @minimum_password_length %> <em>(<%= @minimum_password_length %> characters minimum)</em> <% end %><br /> <%= f.password_field :password, autocomplete: "new-password" %> </div> <div class="field"> <%= f.label :password_confirmation %><br /> <%= f.password_field :password_confirmation, autocomplete: "new-password" %> </div> <div class="actions"> <%= f.submit "Sign up" %> </div> <% end %> <%= render "devise/shared/links" %>Viewから飛んできたnameの情報を受け取れるようにしたいので、
に以下を追記します。
application_controller.rbapplication_controller.rbclass ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? protected def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:name]) end end以下のコードを
application.html.erb
へ記述します。
current_user
はdeviseが提供しているメソッドの一つで、ログインしているユーザーの情報を取得することができます。application.html.erb<% if current_user.present? %> <p>こんにちは、<%= current_user.name %>さん!</p> <% else %> <p>こんにちは、ゲストさん!</p> <% end %>うまく表示されていればOK!
- 投稿日:2020-07-22T17:12:37+09:00
Rails6で作ったアプリをGitHubへプッシュしたい
手順
0.設定 (複数人で開発する場合)
ターミナル$ git config --global user.name "名前" $ git config --global user.email xxx@xxx.com #アドレス1.rails newで作られたgitリポジトリの初期化
ターミナル$ git init #初期化2.gitリポジトリへデータを追加してcommitする
ターミナル$ git status #ファイルの状態を確認する $ git add -A #保存するファイルを指定する -Aは全ファイル追加、ファイル名を指定して1つずつ追加することもできる $ git commit -m "メッセージ内容"3.GitHubでpush先のリポジトリを立ち上げる
4.GitHubへプッシュする
ターミナル$ git remote add origin https://github.com/tkse16/sumple_app.git #GitHubのリポジトリとoriginを紐つける $ git push -u origin master #GitHubへプッシュする。-uはなくてもいい
以上!
- 投稿日:2020-07-22T16:31:46+09:00
RFC emaillバリデーション
#不備メアド 理由 h-1.k-4.@ @前の記号 zkl.106.302.@ @前の記号 gaboratory.u.s.a.@ @前の記号 k.t_0807_@ @前の記号 RFC違反ではない rspone/fine@ 使用不可記号 RFC違反ではない hiro.0308-_-@ @前の記号&記号の連続使用 RFC違反ではない taeko.iwahashi.@ @前の記号 t_h_@ @前の記号 RFC違反ではない t496y519k018...@ @前の記号&記号の連続使用 yu.ya.to.mi.@ @前の記号 akn-szn-sck-hdk...@ @前の記号&記号の連続使用 h-1.k-4.@ zkl.106.302.@ gaboratory.u.s.a.@ k.t_0807_@ rspone/fine@ hiro.0308-_-@ taeko.iwahashi.@ t_h_@ t496y519k018...@ yu.ya.to.mi.@ akn-szn-sck-hdk...@なメアドにバリデーションを引っかからせたい
URI::MailTo::EMAIL_REGEXP
で定義されているところから、ダメな文字の/
を抜いて、@の前に記号が入らないように書きました[4] pry(main)> URI::MailTo::EMAIL_REGEXP => /\A[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\z//\A[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]{0,63}[a-zA-Z0-9]@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\z]/
だけど実際
/
はRFC違反の記号じゃないみたい
ドットの連続が違反らしい@ の前が、最大64文字 @ の後ろが、最大253文字 全部での最大が、最大254文字 ですね。結局
なので結局どうなったかというと
バリデーションは
1,URI::MailTo::EMAIL_REGEXP
を使う
2,URI::MailTo::EMAIL_REGEXP
でチェックできてない、一文字目のドット、連続ドット、@直前のドット の3つを弾くように追加追加バリデーションについて
value =~ /^[.]/ #一文字目のドット禁止 value =~ /\.{2,}/ #連続ドット禁止 value =~ /(?<=\.)@/ #@直前のドット禁止 #肯定後読みこんなURLは全弾きされます
.??..@aa.com
Qiita でまとめてくれている人がいました。
RFC 5322 & 5321に沿ったメールアドレス(local-part)に使える文字まとめ肯定後読み参考url
こんどこそわかる(肯|否)定(先|後)読み
直後に~がある文字列、直後に~がない文字列、にパターンマッチさせる正規表現
- 投稿日:2020-07-22T16:01:25+09:00
【メモ】bundle installとupdateの違い
- 投稿日:2020-07-22T15:27:53+09:00
Railsでアプリケーションを起動する際にffiのインストールに失敗した時の対処法
Ruby on RailsでRailsのバージョンインストールには成功したものの、Railsのアプリケーションを起動した際に以下のようにffiのインストールに失敗しました。
その際に行なった対処法を残しておきます。エラーメッセージ
An error occurred while installing ffi (1.13.1), and Bundler cannot continue. Make sure that `gem install ffi -v '1.13.1' --source 'https://rubygems.org/'` succeeds before bundling.原因
おそらくMacOSのMojave環境に原因があるらしいです。
原因としては、HomebrewでRubyをインストールするとどうやらffiが無いそうです。解決方法
まず、ffiがないのでMojave用のlibffiを再インストールします。以下の方法で再インストールします。
brew reinstall libffiただ、現状ではlibffiのパスが見えない場所にあるので指定します。
export LDFLAGS="-L/usr/local/opt/libffi/lib" && \ export PKG_CONFIG_PATH="/usr/local/opt/libffi/lib/pkgconfig" && \ bundle installこれでffiのインストールエラーがなくなりrailsのアプリケーションが起動できました!
参照記事
- 投稿日:2020-07-22T15:16:25+09:00
いいね機能の実装
目次
ルーティング追加
config/routesresources :post_images, only: [:new, :create, :index, :show] do resource :favorites, only: [:create, :destroy] resources :post_comments, only: [:create, :destroy] endいいねテーブル作成
$ rails g model Favorite user_id:integer post_image_id:integerマイグレーション=>データベース反映
$ rails db:migrateapp/models/user.rbclass User < ApplicationRecord ... has_many :favorites, dependent: :destroy ...app/models/post_image.rbclass PostImage < ApplicationRecord ... has_many :favorites, dependent: :destroy def favorited_by?(user) favorites.where(user_id: user.id).exists? end end ...app/models/favorite.rbclass Favorite < ApplicationRecord belongs_to :user belongs_to :post_image endcontroller作成
$ rails g controller Favoritesapp/controllers/favorites_controller.rbclass FavoritesController < ApplicationController def create post_image = PostImage.find(params[:post_image_id]) favorite = current_user.favorites.new(post_image_id: post_image.id) favorite.save redirect_to post_image_path(post_image) end def destroy post_image = PostImage.find(params[:post_image_id]) favorite = current_user.favorites.find_by(post_image_id: post_image.id) favorite.destroy redirect_to post_image_path(post_image) end endviewを編集
app/views/post_images/show.html.erb<% if @post_image.favorited_by?(current_user) %> <li> <%= link_to post_image_favorites_path(@post_image), method: :delete do %> <i class="fa fa-heart" aria-hidden="true" style="color: red;"></i> <%= @post_image.favorites.count %> いいね <% end %> </li> <% else %> <li> <%= link_to post_image_favorites_path(@post_image), method: :post do %> <i class="fa fa-heart-o" aria-hidden="true"></i> <%= @post_image.favorites.count %> いいね <% end %> </li> <% end %>
- 投稿日:2020-07-22T13:14:42+09:00
Rubyでちょっとずつポーカーを実装する その5
前回の記事
その5のゴール
その1で@r12tkmtさんから受けたアドバイスを元にリファクタリングする。
カードの情報をクラスを切ってもよいかも
Cardクラスを切って、suitとnumberをinitializeで受け取るようにする
カード一枚一枚を連想配列{suit: suit, num: num}で管理していたのを、以下のようなCardクラスに変更しました。
card.rbclass Card SUITS = [:♠︎, :♣︎, :❤, :♦︎] def initialize(suit, num) @num = num @suit = suit end endコミット履歴がこちら
suitはSymbolとし、画面表示用に記号を返却するメソッド
suitのSymbolを表す定数の配列と、それを画面表示用に戻すMARKという定数の連想配列を作りました。ついでに、11はJ(ジャック)、12はQ(クイーン)、13はK(キング)と表示されるようにするようにしました。
card.rbclass Card SUITS = [:spade, :heart, :diamond, :clover] MARK = { spade: "♠", heart: "♡", diamond: "♢", clover: "♣", 1 => "A", 11 => "J", 12 => "Q", 13 => "K" } ~省略~ def show corresponding_mark(@suit) + corresponding_mark(@num) end def corresponding_mark(sym) if MARK[sym] return MARK[sym] else return sym.to_s end end endコミット履歴はこちら
playerが役の判定までがっつり持ちすぎでは?
役の判定を別のmoduleなどに切り出す
役の判定をするplayer.judgeメソッドを、JudgeHandというmoduleを作って分けました。手札を引数として渡すと役が文字列で返ってきます。
結局、このmoduleはplayerクラスがincludeするので、playerが役割持ちすぎ問題は解決していないです?理由はplayerのインスタンス変数のhands配列(手札)の受け渡しが楽なので。どう書いたら良かったのか…コミット履歴はこちら
symbolで役名を返すようにし勝敗判定を数値の比較する
どう書いていいか思い浮かばなかったので、一旦放置としました?
その5のコード
https://github.com/paraizo2424/poker_game/commit/7fc417a84faac4b68a70743b2751fa19d1229340
次回
その6製作中
- 投稿日:2020-07-22T09:18:32+09:00
STIパターンでtweet機能を作った学習記録
この記事の内容は現在レビュー待ちなので変更あるかもです!
STI(単一テーブル継承)とは
文字通り一つのテーブルを親クラスから子クラスに継承させる事!
今回の場合、親Tweet
→ 子Mediatweet
Texttweet
になります。それぞれメディアツイートとテキストツイート用のクラスです!といっても、テーブルが同じなので、親も子も全てのクラスが同じカラムを持ってます。
なのでMediatweet
にもcontent
カラムがありますし、Texttweet
にもimg
カラムがあります。db/schema.rbcreate_table "tweets", options: hogehoge t.text "content" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.string "img" t.integer "user_id" t.string "type" end何故そうするのか
モデル事に差分を書き分けやすくなります。
例えば空欄無効のバリデーションをtexttweetにだけ書くと、画像はテキスト無しのツイートが可能になります。
でもほぼ同じ内容なので、テーブルは共有できちゃうSTIが良いのでは、というわけです。実装方法
1、しれ〜っと
db/schema.rb
に既にありましたが、親クラス(テーブル)にtype
カラム(string型)を持たせます。
2、各モデルを作ります。親モデルはrails g model
などで作って子モデルなどはapp/model/
に直接作りますmodels/tweet.rbclass Tweet < ApplicationRecord endmodels/mediatweet.rbclass Mediatweet < Tweet endmodels/texttweet.rbclass Texttweet < Tweet endとりあえずこれだけでSTIの実装は終わりです。あとは、自動でやってくれます。
例えばこれでMediatweet.create
等でデータベース登録すればtype
カラムにMediatweet
とデータが入ったレコードが追加されます。form_withで取得したparamsの内容でモデル振り分け
メディアデータがあるかどうかで自動的にモデルを振り分けたいので下記のように実装しました。
index.html.erb<%= form_with model: @tweet, url: tweets_path do |form| %> <%= form.text_area :content %><br> <%= form.label "画像をアップロード" %><br> <%= form.file_field :img %> <%= form.submit '投稿' %><br> <% end %>今回は、indexにそのまま投稿フォームがある投稿兼一覧ページです。
画像のアップローダーについてはcarriewave
で実装しています。そこについては割愛します。
上記フォームからsubmitして送られるパラメータは{ "authenticity_token"=>"hoge", "tweet"=>{"content"=>"hoge", "img"=>"hoge"}, "commit"=>"投稿" }こんな感じの二重構造({}の中に{}がある構造)のパラメータが取得できます。
ちなみにですが
form_withで model: を指定するとこのような構造になり、url:のみ指定すると二重構造になりません。
僕はこれがわかってなくてかなりハマってました、、、そしてコントローラーのcreateアクションへ自動でマッチします
tweets_controller.rbdef create @tweet = tweettype_class.new(tweet_params) if @tweet.save redirect_to("/tweets") else @tweets = Tweet.all.order(created_at: :desc) render("index") #レンダリングで元の投稿件一覧ページに戻りますこの際、form_withのurlを指定していないと、 #form_withのルートが子モデルに引っ張られてエラーになります(例)元teets_path レンダリング後→texttweets_pathでno muchになる。 end end private def tweet_params #requireでキーがtweetの部分だけ抽出してます({"content"=>"hoge","img"=>"hoge"}) params.require(:tweet).permit(:content, :img).merge(user_id: @current_user.id) end #[:img]のデータの有無でモデルを振り分けています #二重構造なのでキーも二重に指定しないと正しく値がとれません def tweettype case when params[:tweet][:img].present? 'mediatweet' when params[:tweet][:img].blank? 'texttweet' end end #もらった文字列をキャメルケースに変換して,さらに定数名に変換しています #(例)mediatweet.cmelize=>Mediatweet.constantize=>Mediatweet(id: integer, content: text, created_at: datetime, hogehoge.... ) def tweettype_class tweettype.camelize.constantize endこんな感じで、画像がアップロードされたかどうかで自動でモデルを振り分けてみました。
こうすることで、それぞれのモデルで役割を書きわけやすくなりました!
- 投稿日:2020-07-22T02:08:29+09:00
Rails tutorial 1.3.2でRailsページが表示されない時の対処法
記事作成日:2020/07/21
自己紹介
こんにちは。
最近、プログラミングを勉強し始めたおかむと申します。
初めて記事を投稿するので、拙い部分があるかと思いますがご了承ください。内容
さて、本題に入っていきます。
Ruby on Rails チュートリアル Rails6.0(第6版)にて、Rails tutorialを進めていた途中つまづいた所があったので、つまづいた所と私が行った対処法を共有したいと思います。つまづいた所
Rails tutorial Rails6.0(第6版)←有料のやつ
1.3.2 rails server
という章のrails serverを起動し、Railsページを表示させるところで表示されるページが違うという点でつまづきました。
↓このような画面が表示されるはずが
↓このような画面が表示されてしまいました。
対処法
上のような所でつまづいたのですが、検索をしながら対処したら表示できたので対処法を述べます。
簡単に述べると
- Cloud9を落とす
- awsマネジメントコンソールを開きEC2を検索する
- Cloud9のインスタンスを停止する
- 再度Cloud9で画面を表示するとチュートリアル通りの画面が表示される
このような方法です。
言葉だけだと説明しづらいので以下に画像を載せながら説明していきたいと思います。まず、「Cloud9を落とす」
これはWindowsだったら×ボタン、Macだったら左上の×ボタンをCloud9を開いた状態で押します。
次に「awsマネジメントコンソールを開きEC2を検索する」
これはCloud9を開いた時のようにawsを開き、サービスの検索欄に「EC2」と入力し、そのサービスを開きます。
そして「Cloud9のインスタンスを停止する」
EC2を開いたら、そのアカウントで使用しているインスタンスの一覧が表示されます。
その中で緑色の丸でrunningと書かれているインスタンスがあると思うので、そのインスタンスのチェックマークにチェックをします。
チェックをしたら、画面上部にあるアクションボタンをクリックし、メニューが出てくるので「インスタンスの状態」→「停止」クリックします。
そしたら以下のような警告が出てくると思うのですが、無視して「停止する」ボタンを押してください。
以上の段階を踏み、再度Cloud9で画面を表示するとチュートリアル通りの画面が表示される。
私は、この方法で表示させることができました。
皆様の参考になれば幸いです。参考にした記事
- 投稿日:2020-07-22T00:35:26+09:00
rubyでドローポーカーを作ってみる~準備編~
きっかけ
@paraizo2424
Rubyでちょっとずつポーカーを実装する その1こちらを見たときにいいチャレンジだな~と思いコメントを入れたのですが
はたして自分はアドバイスできるほどの技術力があるのだろうか?
まだまだアドバイスをもらう側ではないのか?と考えたときに、自分ならどう設計・実装するのか?を改めて整理しながら
自分との見つめ合い ということで作ってみようと思いました仕事でrubyを使っていますが、入社当初はruby何それ?って名前くらいしか知らないレベルだったため
そういう意味も込めて振り返れたらなと思いますまずポーカーのルールを整理
参考: https://playingcards.jp/game_rules/drawpoker_rules.html
使用カード
- 全スートの1~13 + ジョーカーあり?
- 今回はjoker無しで行きます
- もしかしたら追加するかも?
プレイヤー
2..7
人ルール
- 各プレイヤーに山札から5枚ずつ配る
- 各プレイヤーは5枚を周りに見せない
- 各プレイヤー順番に、1回のみ好きな枚数カードの交換が可能
- 各プレイヤー手札を見せ、手札の組み合わせ(役)に応じて勝敗が決まる
- 本来はここにベット(掛け金)があり、これを得るための勝負となる
- 今回はひとまずベットも無しとする
役
- 下記の上から順番に強い役となる
- ロイヤルストレートフラッシュ
- 同じマークの10、J、Q、K、Aをそろえる
- ストレートフラッシュ
- 同じマークで、5枚の数字が連続する
- フォーカード
- 同じ数字のカードが4枚ある
- フルハウス
- スリーカードとワンペアが1組ずつできる
- フラッシュ
- 同じマークのカードが5枚ある
- ストレート
- マークに関係なく5枚の数字が連続する
- (10-J-Q-K-AはストレートとなるがQ-K-A-2-3はストレートにならない
- すなわちKとAは連続するがK-A-2含むものはストレートにはならない)
- スリーカード
- 同じ数字のカードが3枚ある
- ツーペア
- 同じ数字のカードが2組ある
- ワンペア
- 同じ数字のカードが1組だけある
- 2人のプレイヤーが同じ役を作った場合は、役を構成しているカードの強い方が勝ちとなる
- カードの強い順位は、A・K・Q・J・10~2
- スートの強い順位は、スペード・ハート・ダイヤ・クローバー
- 役を構成しているカードも同じ場合は、役に使われていないカードが強い方が勝ちとなる
疑問点
- 捨てたカードは公開する?
- 公開しないらしい
- フルハウス同士の比較は?
- フルハウス同士の場合は、3 枚 1 組のカードのランク
- ストレート同士の比較は?
- 一番強いカードで比較
- ストレートの小技
- > 注意: エースに限りランクが一番上または一番下のカードとして使用できます。 最も強いストレートは A-K-Q-J-T (エースハイ)、最も弱いストレートは 5-4-3-2-A (ファイブハイ) です。
参考
- https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q11159415671
- https://www.pokerstars.com/ja/poker/games/rules/hand-rankings/?no_redirect=1今回の実装範囲の決定
- ベットが無いため連続した勝負とはせず1回の勝負の実行とする
- 画面などの作り込むのは面倒なのとrubyのクラス設計・実装などを目的とするため、コンソールでの実行とする
- 全プレイヤーが交換を行うようにしないと最初の5枚で勝負というのは厳しいと思うため、全プレイヤーを実行者が操作するものとする(勝負の意味w)
実装環境の決定
- ruby2.6.5を使用
- test-unitを使用
- 普段は仕事ではrspecしか使用していないためせっかくなので触ってみる
登場人物の整理
なんと表現するのが適切かわかりませんが、モノと、モノではないもの(= 概念)という意味で分けてみる
モノ
- カード
- プレイヤー
概念
- 山札
- カード全て
- 手札
- 山札から配られた各プレイヤーの手札(5枚)
- 役
- 手札の中の組み合わせ
- 下記を持つ
- 役の種別
- 役の中での強さ
ソース
https://github.com/rytkmt/ruby_poker
これから作っていくため、まだ空で作成したのみです。
ここから。楽しみ。続き
- 投稿日:2020-07-22T00:12:28+09:00
【Rails】devise 導入方法
deviseとは
- Railsで作成したアプリケーションへ簡単に認証機能を実装することができるgem(ライブラリ)の一つ
- ログイン、サインアップなどのログイン機能が作成出来る
導入方法
①Gemfileに追加
Gemfilegem 'devise'② bundle install を実行
ターミナル$ bundle install③設定ファイルを作成
ターミナル$ rails g devise:install新規作成されるファイルconfig/initializers/devise.rb config/locales/devise.en.yml④deviseの機能を持ったUserモデルを作成
ターミナル$ rails g devise user新規作成されるファイルapp/models/user.rb db/migrate/20XXXXXXXXXXXX_devise_create_users.rb test/fixtures/users.yml test/models/user_test.rb⑤deviseのviewファイルを作成
ターミナル$ rails g devise:views新規作成されるファイルapp/views/devise #以下のディレクトリにあるビューファイル各種これで、deviseの導入が完了します