- 投稿日:2019-12-24T22:57:04+09:00
EC2でsassc gemがインストール アップデートできなかった
解決策
古いgccを捨てて新しいgccをインストールする
yum remove gcc libstdc++48-devel yum install gcc72 gcc72-c++ libstdc++72事象
lto1: fatal error: LTO_tags out of range: Range is 0 to 365, value is 51456 compilation terminated. lto-wrapper: g++ returned 1 exit status /usr/bin/ld: lto-wrapper failed collect2: error: ld returned 1 exit status make: *** [libsass.so] Error 1 make failed, exit code 2 Gem files will remain installed in /home/enechange/enechange/rails/vendor/bundle/ruby/2.6.0/gems/sassc-2.2.1 for inspection. Results logged to /home/enechange/enechange/rails/vendor/bundle/ruby/2.6.0/extensions/x86_64-linux/2.6.0/sassc-2.2.1/gem_make.out An error occurred while installing sassc (2.2.1), and Bundler cannot continue. Make sure that `gem install sassc -v '2.2.1' --source 'https://rubygems.org/'` succeeds before bundling. In Gemfile: activeadmin was resolved to 2.4.0, which depends on sassc-rails was resolved to 2.1.2, which depends on sassc
- 投稿日:2019-12-24T22:51:20+09:00
Railsアプリ、EC2の本番環境でMySQLを直接いじる方法
自分の作ったアプリを本番環境で起動させた時に、エラーが出てしまうことはあると思います。
currentフォルダ内のlogを見て、エラーを発見したとしても、それがMySQLのエラーだと直接コマンド打たないと行けないので、修正が結構めんどくさいんですよね!そこで、よく使うMySQLのコマンドをまとめました!
ログイン
$ mysql -u root -p Enter password: パスワードを入力(実際に文字は見えません)データベースの確認
show databases;以下のように出てきます
+----------------------------------+ | Database | +----------------------------------+ | information_schema | | xxxxxxxxxxxxxxxxxx| | mysql | | performance_schema | +----------------------------------+いじるデータベースの選択
上のコマンドで確認したものの中から選びます
use データベース名;これが出ればOKです
Database changedテーブル一覧表示
show tables;データの検索
とりあえずテーブルの中身を全部見たい場合は
SELECT * FROM テーブル名;データの削除
僕がよく使うのはこれ、とりあえずエラー引き起こすデータは消してしまえ精神
DELETE FROM テーブル名 WHERE id = 数字;補足
この方法はあくまで、直接いじらないと直せないエラーなどを解決する方法です!
Railsの場合は、カラムの追加やテーブルの新規作成などは
マイグレーションファイルを使っていつものようにやれば大丈夫です。
- 投稿日:2019-12-24T22:38:34+09:00
キーワード引数というコントリビュートチャンス
Ruby2.7へ向けて
そろそろRuby2.7の季節がやってきましたね!
すでにアップデート準備はすませましたでしょうか?Ruby3へ向けた移行パスの一環として、Ruby2.7からキーワード引数の仕様が変更されます。
これは多少の破壊的な変更があり、実際にRubyのアップデートを考えるときには悩みどころとなりそうです。ここでは実際に手元のアプリケーションをRuby2.7.0-rc2に移行する際に遭遇したケースをヒントに、自分なりに想定できる対処方法などを記してみます。
※ なおキーワード引数の新仕様や、その具体的な挙動については、こちらに素晴らしくわかりやすい解説記事があるので参考にしてみてください。
https://tmtms.hatenablog.com/entry/201912/ruby27-keyword-argument
https://qiita.com/jnchito/items/c15ac23791e0320e0fc2
なおこの移行に伴い、たくさんのgemにおいてコントリビュートチャンス祭りが発生すると思われます
Rubyエンジニアの皆様にはぜひぜひこの機会を生かしていただければと思っています!なぜ変更されるのか
(私自身はただの傍観者なのですが)、外野から理解できている範囲では、Rubyコアコミッターであるmametterさんが2018年ごろから精力的に提案されていた問題提起がきっかけとなっているのかなと思います。
詳細は上にも書いた素晴らしい記事があるので省略しますが、ここにある3種類のポイントが主に解決すべき問題ということのようです。
非常に雑にアウトラインをまとめると、
- 現在のキーワード引数には、いくつかのケースにおいてbuggyな挙動がある
- これらは現在の仕様に全体的な整合性がないことに起因している
- キーワード引数の現仕様になんらかの破壊的変更をして新仕様に移行するしか、これらの問題を解決できない
という話のようです。
該当チケットを追いかけると、変更のインパクトをなるべく最小限に抑えつつ(下方互換を最大限に保ちつつ)、全体的に整合性のある仕様にする(さらにRuby2.7において適切なマイグレーションパスをつくる)ことにむけた議論がなされています。
Ruby仕様に精通したコミッターチームであっても、これはかなり難解な挑戦になっていたようで、様々なアイデアや非常に細かいコーナーケースの検証を経てようやく着地したのが現在の仕様のようです。(チケット上で紹介されているこちらの資料には、新仕様の方向性についてのわかりやすいまとめがあります)
仕様の勘所
具体的には冒頭でリンクした良記事や、公式リリースにおける解説を参考にしていただくのがよさそうです。
大きくはRuby3におけるキーワード引数の勘所として、以下のことを理解しておくといいかと思います。
前提
- メソッドは以下の3種類の引数を持てる
- ポジショナル引数 ・・・ 位置関係によってきまる(順序を変更すると異なる引数になる)
- キーワード引数 ・・・ キーワードラベルによって指定できる(キーワードの順序を変更できる)
- ブロック引数 ・・・ 1つだけブロックを受け取れる
この中で、主にキーワード引数の受け渡しに関連して、以下のように仕様が整理されました。
仕様A(大原則)
- キーワード引数を受け取るメソッドに対して、キーワード引数を渡す際には、ハッシュオブジェクトではなくキーワード引数として渡さなければならない
- Ruby2.6までは、ハッシュオブジェクトを(ポジショナル引数の最後にセットして)渡すと、キーワード引数として解釈してくれた
- Ruby2.7ではポジショナル引数の最後にハッシュオブジェクトを渡すと警告付きでキーワード引数として解釈する
- 来年に出る予定のRuby3からは、ハッシュオブジェクトを渡すとポジショナル引数として解釈される
仕様B(例外ルール)
- キーワード引数を受け取らないメソッドに限っては、キーワード引数を渡した場合に、ハッシュオブジェクトに変換したものをポジショナル引数として受け取れる
- このケースについてはRuby2.6以下と下方互換性が保たれる
仕様C
- キーワード引数のキーは、シンボル以外のオブジェクトでも受け付ける(Ryby2.7以降の新仕様)
何が困るのか
主に、キーワード引数が必要なメソッドにハッシュオブジェクトを渡しているパターンがあります。
たとえばRailsなんかでよくあるコントローラーだと、ハッシュを別のメソッドに引数として渡す局面があります。def create user = assign_attributes(user_params.to_h.merge(role: :admin)) # 呼び出し側はハッシュオブジェクトを渡す user.save! end private def assign_attributes(**params) # 受け取り側はキーワード引数を要求 User.new **params endこれに対して警告が出ます(Ruby3でエラーになる予定)
warning: The last argument is used as keyword parameters; maybe ** should be added to the call warning: The called method `assign_attributes' is defined here対策としては、
def create user = assign_attributes(**user_params.to_h.merge(role: :admin)) # **演算子でハッシュオブジェクトを開く(キーワード化する) user.save! endということが必要になります。
実際にアプリケーションで使用していたActsAsTenantというgemにおいてこのタイプの警告が出ていたので、簡単なパッチを書きました。
delegationに気をつけろ
さて、この手の警告に関して少し対処が難しいケースがdelegationです。
ここでいうdelegationとは、「あるメソッドが受け取った引数(の全部または一部)を引数として別メソッドを呼び出す」というパターンを指します。
たとえば典型的にこういうコードがあります。
class APIWrapper def execute(*args) ExternalAPIClass.new.delegated_method(*args) # 丸投げする end end
ExternalAPIClass#delegated_method
がキーワード引数をとるメソッドだった場合、これでは正しく呼び出すことができなくなります。
(Ruby2.7では警告付きで動きますが、Ruby3系で死ぬことになります)いくつか対処法がありますが、Ruby2.7以降を使う場合は
class APIWrapper def execute(...) ExternalAPIClass.new.delegated_method(...) end end引数を「丸投げ」する記法、
...
で対応できます。ただし、この
...
だと選択的に一部の引数だけ取り出す、ということができません。たとえば・・class APIWrapper def execute(*args, external: true) if external ExternalAPIClass.new.delegated_method(*args) # 丸投げする else # ...snip end end endこの局面では
...
で一部の引数を引き取ることができないため、Ruby2.7以降で動かしたい場合は明示的にclass APIWrapper def execute(*args, **kwargs, external: true) if external ExternalAPIClass.new.delegated_method(*args, **kwargs) # 丸投げする else # ...snip end end endという対処が必要になります。
なお、ここでいうdelegationとは「委譲と継承」という文脈における委譲ではありません。
つまり継承においてもこの問題が起こる可能性があります。(実際にあるgemで踏みました)class ParserBase def parse!(*args, failure: :exception) # ...snip end end class Parser < ParserBase def parse!(*args) super # 暗黙のうちにargsを渡している end endこのようにdelegationが絡むと問題が発生することがあるのでアップデート時には気をつけてください。
難しいケース
通常のアプリケーションにおいては、「RubyアップデートのPRで、全てのコードをRuby2.7仕様に変更」という対処が可能です。
ただ様々なRubyバージョンで利用されるgemについては、現在のバージョンでの後方互換を保ちつつ、新バージョンにも対応できるパッチを入れたい、ということで少し難しい対処が必要になることがありそうです。
複数のRubyバージョン対応を意識したdelegation
これが現時点わかっている限り、かなり難しい対処を要求されそうなパターンです。
詳細の議論は省きますが、この記事に非常に良いまとめがあるので一読をオススメします。
https://eregon.me/blog/2019/11/10/the-delegation-challenge-of-ruby27.html
(なお記事で触れられているpass_keywordsというアイデアはおそらくなくなったようで、Ruby2.7以降においては、
ruby2_keywords
がRuby2.6 EOLの時点までの期間限定で導入されるという方向になった模様です)※ なおRuby2.6以下での対処について、Ruby2.6以下にバックポートされると書きましたが、実際はバックポートされないとのことです。旧バージョンでも対応するためには、下のコード例にあるように自前でruby2_keywordsを定義するか、ruby2_keywords gemを利用することで対応することになります 1
対処例
このサンプルは、上の記事から引用しています。
もしwrapper_methodというメソッドが(引数を丸投げした上で)target_methodというメソッドを呼び出しているとして・・・以下のようなコードを書くことで、Ruby2.6までの挙動を担保しつつ、今回のRuby2.7や、Ruby3(とくにRuby2.6がサポート対象外となるとされるRuby3.2系以降の世界)においても、意図通りに「引数を正しく丸投げするdelegation」を実現ことができそうです。
def ruby2_keywords(*); end unless respond_to?(:ruby2_keywords, true) if RUBY_VERSION < "3" ruby2_keywords def wrapper_method(*args, &block) target_method(*args, &block) end else def wrapper_method(*args, **kwargs, &block) target_method(*args, **kwargs, &block) end endなおこのRUBY_VERSIONを元にした条件分岐ですが、Ruby2.6がEOLになった未来(Ruby3.2登場のタイミング?)において、「ruby2_keywordsという(醜い)移行用メソッドは消滅させる」ということをmatzが明言しているために存在しています。
もし「Ruby3.1あたりでRuby2.6の下方互換を捨てる」という判断であればこの分岐は必要なさそうです。(その時点でelse節だけにすればよい)実世界での対処
すでに一部のgem、たとえばrails本体ではこのメソッドをつかった数々の改修がなされているのを見ることができます。他にもdry-rbにおいてもこの手の改修を見ることができますね。
言いたいこと
ぜひ手元のアプリケーションをRuby2.7にアップデートして、警告を注視してください。
最初のうちは、アプリケーションだけでなくgem関連でも警告が出ると思います。これらが全て改修対象になります。
我々のような一開発者でも、ガシガシとコントリビュートできるこの大チャンスを生かさない手はない!(鼻息)
みんなであちこちのgemをアップデートして、きたるRuby3へ向けてスムーズな移行ができる世界を目指せるといいですね。
(蛇足ながら、もし記事の誤りに気づかれたら、コメント欄などでご指摘いただければ助かります)
最後に
この記事を書くにあたって、この二年間の様々な努力の跡を眺めながら巨人の足跡をたどらせていただきました。
様々な問題を根絶すべく、この難解な仕様変更を根気よく詰めてこられたRubyコミッター陣にはただただ頭が下がります。
またいち早くこの難しい変更を取り込み、移行の努力をされているRailsやRubocopなど著名gemのメンテナーの方々には感謝しきれません。ありがとうございます。
検証環境
この記事では、手元の(Rails)アプリケーションに試験的にruby2.7.0-rc2を投入して、Ruby2.7.0-rc2へのアップデートにトライしています。
Ruby2.6.5でRSpecがオールグリーンの状態から出発して、Ruby2.7.0-rc2において、キーワード引数関係の警告がどうなるかを検証し、いくつか引っかかったケースを元に対処法を記事にしています。
もともとはRails6.0.2系のアプリケーションでしたが、キーワード引数の変更に対応させるためRailsバージョンは2019/12/21時点のmasterまで引き上げています。
コミッターのn0kadaさんが指摘されているツイートを偶然目にして、記事を訂正させていただきました。ありがとうございます。 ↩
- 投稿日:2019-12-24T21:00:49+09:00
データベース--メモ
DBでよく使う型
string 文字列型
text テキスト(不定長文字列)型
integer 整数型
float 浮動小数点数型
decimal 固定長整数型
datetime 日時型
timestamp タイムスタンプ型
time 時刻型
date 日付型
binary バイナリ文字列型
boolean 真偽値型
references 他のテーブルへの外部キーの定義、_id が付いた整数電話番号の型を設定するとき
integer型だと10桁まで。電話番号は11桁の時もある。なのでDBに登録できないことがある。
また、09012345678の場合、最初の0が消えることがある。なので string型をおすすめします。
string型の文字列だとハイフン(ー)もDBに保存ができる。
def change
create_table :addresses do |t|
t.references :user, foreign_key: true, null: false
t.string :post_code, null: false
t.string :prefectures, null: false
t.string :city, null: false
t.string :address, null: false
t.string :building_name
t.string :tel_no
t.timestamps
end
end
参考記事
https://qiita.com/Yinaura/items/cede8324d08993d2065c
https://chinatz.hatenablog.com/entry/2018/07/20/210000
- 投稿日:2019-12-24T20:44:20+09:00
Ruby testコマンド じゃんけん
じゃんけんのプログラムのテストを実行
1.テストする、じゃんけんのプログラム
参照
https://qiita.com/ktpnobu/items/a8525f8cceddd486c219#Rock-Paper-Scissors #0-1-2 p 'rock-paper-scissors' player=gets a=0 if player=="rock" player_hand=0 a=1 elsif player=="paper" player_hand=1 a=1 elsif player=="scissors" player_hand=2 a=1 end program_hand=rand(3) if program_hand==0 program="rock" elsif program_hand==1 program="paper" elsif program_hand==2 program="scissors" end if a==0 p 'one more please' if player_hand==program_hand 'draw' elsif (player_hand==0 and program_hand==2) or (player_hand==1 and program_hand==0) or (player_hand==2 and program_hand==1) 'you win' else 'you lose' end p "player",player,"program",programこのプログラムの勝敗の部分について、test実行
2.test/unit
test/unitの読み込み
require 'test/unit'3.引数から答えをだす関数を定義←これが正しいのかテストする
今回、じゃんけんの勝敗のテストなので、二つの手を引数とし、勝敗が正しく表示されるか
class Rock_paper_scissors def self.hantei(player_hand,program_hand) #ここにプログラムを書く end endplayer_handとprogram_handの2値が決まれば、正しい勝敗が出る(であろう)関数である
↑これが本当に正しいのかをtestするイメージ関数の中身
if player_hand==program_hand 'draw' elsif (player_hand==0 and program_hand==2) or (player_hand==1 and program_hand==0) or (player_hand==2 and program_hand==1) 'you win' else 'you lose' end↑player_handとprogram_handの2つから、結果を出すもの
上二つをまとめると、class Rock_paper_scissors def self.hantei(player_hand,program_hand) if player_hand==program_hand 'draw' elsif (player_hand==0 and program_hand==2) or (player_hand==1 and program_hand==0) or (player_hand==2 and program_hand==1) 'you win' else 'you lose' end end end4.検証プログラム
2つの引数からでた値は本当に正しいのか検証するために、
すべての出力とその結果を書いたプログラムを作るclass TestRock_paper_scissors < Test::Unit::TestCase def test_Rock_paper_scissors assert_equal('draw', Rock_paper_scissors.hantei(0, 0)) assert_equal('draw', Rock_paper_scissors.hantei(1, 1)) assert_equal('draw', Rock_paper_scissors.hantei(2,2)) assert_equal('you win', Rock_paper_scissors.hantei(1, 0)) assert_equal('you win', Rock_paper_scissors.hantei(2, 1)) assert_equal('you win', Rock_paper_scissors.hantei(0, 2)) assert_equal('you lose', Rock_paper_scissors.hantei(2, 0)) assert_equal('you lose', Rock_paper_scissors.hantei(0, 1)) assert_equal('you lose', Rock_paper_scissors.hantei(1, 2)) end end5.まとめ
require 'test/unit' class Rock_paper_scissors def self.hantei(player_hand,program_hand) if player_hand==program_hand 'draw' elsif (player_hand==0 and program_hand==2) or (player_hand==1 and program_hand==0) or (player_hand==2 and program_hand==1) 'you win' else 'you lose' end end end #Rock-Paper-Scissors #0-1-2 class TestRock_paper_scissors < Test::Unit::TestCase def test_Rock_paper_scissors assert_equal('draw', Rock_paper_scissors.hantei(0, 0)) assert_equal('draw', Rock_paper_scissors.hantei(1, 1)) assert_equal('draw', Rock_paper_scissors.hantei(2,2)) assert_equal('you win', Rock_paper_scissors.hantei(1, 0)) assert_equal('you win', Rock_paper_scissors.hantei(2, 1)) assert_equal('you win', Rock_paper_scissors.hantei(0, 2)) assert_equal('you lose', Rock_paper_scissors.hantei(2, 0)) assert_equal('you lose', Rock_paper_scissors.hantei(0, 1)) assert_equal('you lose', Rock_paper_scissors.hantei(1, 2)) end end6.参考文献
https://qiita.com/repeatedly/items/727b08599d87af7fa671#assert%E3%81%AE%E4%BD%9C%E3%82%8A%E6%96%B9
https://teratail.com/questions/197215
http://portaltan.hatenablog.com/entry/2015/10/28/111757
https://teratail.com/questions/164134
- 投稿日:2019-12-24T17:37:26+09:00
世の中の1%の人の為になるかもしれないRails tips
はじめに
本記事はZeals Advent Calendar 2019の24日目です。メリークリスマスイブ!
本記事では僕が日々開発しているなかで、どうしてもなんとかしなければいけない…という時に活用したtipsをまとめたものです。
タイトルにある通り、エッジケースすぎるのでもし同じような状況で困っている人の為になれたら幸いです。環境
Rails 5.2.3
Ruby 2.5.5tips
といっても今回紹介するのは2つです
- カラムにaliasをかける
- caller
カラムにaliasをかける
Railsではcolumnに対してaliasを貼ることができます。
モデル名 カラム名 Button name Choice label このようなモデルが2つあるとします。
カラム名はそれぞれ違いますが同じものとして扱いたい時があると思います。
そんな時はalias_attribute が使えます。class Choice < ActiveRecord::Base alias_attribute :name, :label
alias_attribute
を使うことでChoiceのlabel
をname
として扱うことができるようになります。
ただname
として扱うことができるようになるだけではなくChoice.first.name? => trueといったActiveRecordの述語メソッドやゲッターやセッターを生成してくれます。
ただ同名として扱いたいだけで、述語メソッドを生成するまでもない場合は以下のようにシンプルに定義するだけで良さそうです。class Choice < ApplicationRecord def name label endカラム名を変更したい。変更したほうが良い。でも
rename_column
してる時間ないよぉ…
という時に使いました、便利ですねーcaller
モデル名 カラム名 User cliend_id
モデル名 カラム名 Answer client_id
モデル名 カラム名 カラム名 UserAnswer user_id answer_id このような中間テーブルがあるとします。
以下はmodelclass UserAnswer < ApplicationRecord belongs_to :user belongs_to :answer validates :should_be_same_client def self.import_associations!(users, answer) associations = users.map do |user| UserAnswer.new(user_id: user.id, answer_id: answer.id) end UserAnswer.bulk_import! associations, validate: false end def should_be_same_client return user.client == answer.client errors[:base] << ' client_id of user and client_id of answer are different' end end定義されている
import_associations!
は引数としてuserの配列と指定のanswer(回答)を受け取ります。カスタムのvalidationメソッドが定義されており、
User
もAnswer
もClient
に対してbelongs_to
の関係で、client_id
が違うものが作成されないようにしています。
should_be_same_client
はUserAnswer単体を生成する時は動いてほしいものですが、bulk import
で作成したい時はvalidationをskipしたい。カスタムvalidationも動いてほしくないとします。Railsのversionは5.2系なので(早く6に上げたい…)bulk importはactiverecord-importを利用します。
bulk_importはoptionで
validate: false
を渡すことでmodelのvalidationをskipさせることができます。
しかし今回定義したようなカスタムvalidationはskipしてはくれません。
bulk importする件数が数件であれば都度カスタムvalidationが走ったところで問題はないのですが、件数が多くなればなるほどN+1で都度カスタムvalidationが走るにより実行時間がどんどん長くなるので避けなければなりません。つまり
bulk_import
を呼び出すメソッドのときのみカスタムvalidationをskipする必要があります。そこでcallerを使いました。
callerはバックトレースの情報を確認することができるので、どのメソッドから呼び出されたのか,どのメソッドを経由してきたのかを確認することができますcallerで呼び出されたバックトレースを正規表現を使って、メソッド名だけを見れるようにします
caller.map { |c| c[/`([^']*)'/, 1] }=> ["eval", "evaluate_ruby", "handle_line", "block (2 levels) in eval", "catch", "block in eval", "catch", "eval", "block in repl", "loop", "repl", "block in start", "__with_ownership", "with_ownership", .......これでどのメソッドを経由してきたのかがわかります。
取得したバックトレースないにvalidateをskipしたいメソッドが含まれているかを確認するメソッドを追加し、def call_from_import_associations? caller.map { |c| c[/`([^']*)'/, 1] }.include?('import_associations!') endvalidates :should_be_same_client, unless: call_from_import_associations?カスタムvalidationの発火条件として追加し、
import_associations!
から呼び出されるときのみカスタムvalidationが発火しないようにすることができます。まとめ
実際にこのユーザーに対してtagを一括でつける機能は使用頻度の高い機能だったので、早急に修正する必要があり、本当になんとかするためにひねり出した苦肉の策ではあります…w
早急に対応しなければいけない。なんとかしなければいけない。という同じような問題に詰まっている人たち、もしくは同じようなケースで困っているの助けに少しでもなっていれば幸いです!
明日はついにAdvent Calendarの最終日です!
最終日は@pannpersです!
それでは良いクリスマスイブを!
- 投稿日:2019-12-24T17:19:37+09:00
Youtuber向けにサービス開発したが需要が無かった話
はじめに
プログラミング始めて半年くらいで自分の好きなタイ在住日本人Youtuberに使ってもらいたくて開発した時の話です。結論から言うと上手くいかなかったです。その原因としてリサーチ不足と自己満の開発であったからです。今後サービス開発をする人にとって今回の自分の経験が役立てばいいなと思い、今回のことの経緯をまとめておきます。
結論
・競合サービスのリサーチが大切
・ユーザーにしっかりとリサーチすることも大切
・自己満のサービスになっていないか何を作ったのか
私は1つのMAP上でYoutuberは撮影した場所にピンをさすことができ、そこをクリックすると詳細情報が見れて最終的にはYoutubeチャンネルに移動する。そして視聴者はYoutuberに撮影して欲しい場所に対してピンを挿してリクエストできる双方向コミュニケーションサービスを開発しました。
難易度
サービス自体はRuby on Railsで開発を行いProgate卒業すれば作れるような簡単なものでした。
MapにはGoogle map APIを使用しました。
そして機能自体もシンプルだったので開発自体はそれほど時間がかかりませんでした。実際に提案してみて
この時の最大にミスとしては自分が作りたいものを作ってから、これYoutuber向けに改良すれば良いだろうと仮説を立て開発してから売り込んだことです。そのため世界的な大企業が競合となるとも知らずに自信満々にサービスの提案をYoutuberの方に行いました。
結果
・Google MapにあるMy Mapと言う似たような機能使っているが、それとは何が違うか聞かれ答えられなかった。
→正直自分でもGoogle Mapで十分だと感じました。
・Youtuberがこのサービスを「使うメリットが全くなかった。そこからの改善
せっかく作ったので諦めたくないなと思い、Youtuberの方にヒアリングなどを行い何か改善点はないか探りました。その結果1つだけありました。
それは視聴者の目線に立つことでした。
そのYoutubeチャンネルの視聴者は日本人だけではなく、タイ人の方もおられました。
そのことに関してリサーチを行った結果、Yotuberの方はタイ人視聴者からの撮影して欲しいリクエストが多くあるみたいですが、詳細情報が聞けずに諦めていたようです。さらに視聴者としてもここを動画にして欲しいなどの思いがあったみたいです。ここから私はタイ人視聴者と日本人Yotuberがコミュニケーションできるサービスに方向転換をしました。最終結果
最終的には良いサービスじゃん!とYoutuberの方に言って頂きました。
そしてリリースに向けて準備していたときに、Youtuberの方が解散をされてしまいました、、、
しかもやりとりをしていた方が脱退という形だったので大打撃でした。
結局その後いろいろありましたが、幻のサービスとして世に出ることはありませんでした。学び
結果的には失敗だったのですがたくさんの学びがありました。
それはサービスを開発する上でユーザーの立場になって考えるのは大切ということです。
こんな技術を使いたい!これ面白そう!って言うのが理由で開発も素晴らしいです。しかし誰かに使ってもらう前提で作るなら、徹底的にリサーチを行い他に似たサービスがないか調査するのは必要だなと思います。これは今後開発以外の場面であっても使えそうな考え方かなと思います。
最後に
今回かなり短い文面にはなりましたが最後までみていただきありがとうございます。
インターネットに関して全くの知識がなかった自分がサービスを作れるようになったと考えると感慨深いです。テクノロジーを使えば世の中にとって役に立つことを生み出すことができます。
しかし役に立つものしか生み出してはいけないかと言われると違うと考えてます。
意味のわからない、誰が使うかもわからないサービスであっても誰かの役に立つかもしれません。今回リサーチは大事や自己満で作るな。どと言っておきながら、
最後に矛盾したことを言いますが楽しかったら全てokです!メリークリスマス!!
- 投稿日:2019-12-24T17:02:48+09:00
Railsチュートリアルを必要コマンドだけをまとめて記しました。第1章
Railsチュートリアル 対象者 中級者向け(最低でもRuby, HTML/CSS, Railsの基礎) 何ができるか 本格的なアプリを開発でき、最終的にWeb上に公開できる 開発環境 AWS Cloud9 or ローカル環境 1.2.2 Railsをインストールする
リスト 1.1: バージョンを指定してRailsをインストールする $ gem install rails -v 5.1.6
リスト 1.3: rails newを実行する (バージョン番号を指定) $ cd ~/environment $ rails 5.1.6 new hello_app cdはchange directly(このディレクトリに移動する)
〜はホームディレクトリ(自分のアカウントのデフォルト)1.3.1 Bundler
リスト 1.5: Ruby gemごとにバージョンを明示的に指定したGemfile(リスト 1.4のGemfileをリスト 1.5に置き換えます。) $ c9 open Gemfile source 'https://rubygems.org' gem 'rails', '5.1.6' gem 'puma', '3.9.1' gem 'sass-rails', '5.0.6' gem 'uglifier', '3.2.0' gem 'coffee-rails', '4.2.2' gem 'jquery-rails', '4.3.1' gem 'turbolinks', '5.0.1' gem 'jbuilder', '2.6.4' group :development, :test do gem 'sqlite3', '1.3.13' gem 'byebug', '9.0.6', platform: :mri end group :development do gem 'web-console', '3.5.1' gem 'listen', '3.1.5' gem 'spring', '2.0.2' gem 'spring-watcher-listen', '2.0.1' end # Windows環境ではtzinfo-dataというgemを含める必要があります gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]チュートリアルではRailsを学ぶために勉強しているので、
リスト 1.4: hello_appディレクトリにあるデフォルトのGemfile をリスト 1.5のように固定します。
アプリケーションのGemfileの内容をリスト 1.5で置き換えたら、bundle installを実行してgemをインストール $ cd hello_app/ $ bundle install bundle installを実行すると、先程全部のVerを固定するというGemfileの内容を書き換えたので、その固定されたVerだけを取って来てくれます。
Bundler could not find complete versions for gem "activesupport":
In snapshot (Gemfile.lock):
In Gemfile:
rails (=5.1.4) was resolved to 5.1.4, which depends on railties (>= 4.0.0) was resolved to 5.1.6, which depends on activesupport(=5.1.6)
coffee-rails (= 4.2.2) was resolved to 4.2.2, which depends on railties (>= 4.0.0) was resolved to 5.1.6, which depends on activesupport(=5.1.6)
Running 'bundle update' will rebuild your snapshot from scratch, using only the gems in your Gem file, which may resolve the conflict.ただ実行するとほとんどの場合がうまく動かず、先程あなたはrails 5.1.6を入れたのに5.1.4を入れようとすると依存関係がごちゃごちゃになるから困っちゃった、とbundleから言われます。
「直したいんだったら”bundle update”を実行してください。実行するともう一回Gemfileを一から読み直してくれるので直るんじゃないですか」というのが最後の行に書かれています。
$ bundle update bundle updateを実行したことにより、全てのgemのVerが揃い、これでrailsが立ち上がります。ここまで実行したことはつまり、リスト1.3のrails newコマンドとリスト1.5のbundle installコマンドを実行したことにより、実際に動かすことのできるアプリケーションが作成されたということです。
1.3.2 rails server
リスト 1.6: Railsサーバーを実行する $ cd ~/environment/hello_app/ $ rails server railsサーバーが立ち上がったか確認するために、Preview画面をクリックして、Preview Running Applicationをクリックします。
アプリケーションをブラウザで開く
rails serverを実行したときのデフォルトのRailsページ
c9コマンドをインストールするコマンド $ npm install -g c9 npmはnode.jsに於ける管理ツールでc9はnpmの一環として管理されているので、npmによりc9コマンドが入れられるようになります。
リスト 1.15: クラウドIDE上でHerokuをインストールするコマンド $ source <(curl -sL https://cdn.learnenough.com/heroku_install)
herokuが正しくインストールできていれば、バージョン番号が表示されるようになります $ heroku --version heroku-cli/6.15.5 (linux-x64) node-v9.2.1 1.3.3 Model-View-Controller (MVC)
railsの中にはModel、View、Controllerがあり、上手く作用しています。ソースコードが煩雑にならず、3つそれぞれに切り替えられます。
1.3.4 Hello, world!
リスト 1.7: Applicationコントローラにhelloを追加する $ c9 open app/controllers/application_controller.rb class ApplicationController < ActionController::Base protect_from_forgery with: :exception def hello render html: "hello, world!" end endrenderは”描画する”という意味です。
リスト 1.9: ルートルーティングを設定する(リスト 1.8から1.9に書き換えます) $ c9 open config/routes.rb Rails.application.routes.draw do root 'application#hello' endrouteとrootを区別するため、訳文ではrouteを基本的に「ルーティング」と訳します。「config/routes.rb」をRailsのルーティングファイルと呼びます。
「application#hello」は、Applicationコントローラファイルの中にあるhelloメソッドを呼び出してくださいという意味です。
先程の”Yay! You’re on Rails!”はrootURLで、
したがって、「root 'application#hello'」は、rootURLにアクセスが来たら、Applicationコントローラファイルのhelloメソッドを呼び出してくださいということになります。
リスト 1.9の後、rails serverを実行すると、「hello, world!」と表示されます。1.4 Gitによるバージョン管理
次は「hello, world!」を開発環境のcloud9じゃなくて本番まで持っていきましょう。本番環境は「heroku」を使いますが、「heroku」という本番環境に自分たちの”hello_app”を載せるためにはGitというソフトウェアが必要になります。
Gitはゲームでいうセーブポイントで、ボス戦が入る前とかにセーブして負けたとしても、ロードしてやり直せる。ような感じです。普通のゲームと違うのはゲームだとセーブポイント10個くらいに収まるが、Gitの場合は1000個とかになリます。
ここからこのGitを使ってセーブやロードができる環境を作る初期化を行うコマンド $ git init
今Gitに入っていないファイルがこれだけあるよというのが確認できる $ git status
セーブする対象を定義する $ git add -A
確認したらこれまで行ったものをセーブする $ git commit git commitを実行すると、nanoかVimが出て来ます。
nano から vim に変更するには、こちらの記事を参考に以下のコマンドを実行します。
$ git config --global core.editor 'vim -c "set fenc=utf-8"'
Vimの場合はAなどを押すと、「INSERTモード」になり、後から見て思い出せるようにコメントしておきます。
この後、保存してエディタから出るには、ESCキーを押して、最初のモードに戻り、「:wq」を押します。
「:」はこの後コマンド入力する、「w」はコメントを書き込む、「」はこのエディタから出る、という意味です。
この日にコミットしたコミットlog(これまでのセーブしたプログラム)を見ることができる $ git log ただこれらのGitは開発環境の中でしかファイルやセーブデータが見れないため、例えば、アカウントを消してしまうと、このGitのセーブポイントのデータが消えてしまいます。ですので、万が一のために、基本的に作ったGitのレポジトリは自分たちの開発環境とは別のところに置きます。なので、何らかの理由で開発環境が使えなくなったとしても、そのサーバーに残しておけば、みんなで見れてすぐに再現できます。
そういったサーバー(レポジトリ置き場)で有名なのがGitHubです。1.5 デプロイする
リスト 1.13: 追加や並び替えを行ったGemfile(Gemfileをリスト 1.13に書き換える) $ c9 open Gemfile source 'https://rubygems.org' gem 'rails', '5.1.6' gem 'puma', '3.9.1' gem 'sass-rails', '5.0.6' gem 'uglifier', '3.2.0' gem 'coffee-rails', '4.2.2' gem 'jquery-rails', '4.3.1' gem 'turbolinks', '5.0.1' gem 'jbuilder', '2.7.0' group :development, :test do gem 'sqlite3', '1.3.13' gem 'byebug', '9.0.6', platform: :mri end group :development do gem 'web-console', '3.5.1' gem 'listen', '3.1.5' gem 'spring', '2.0.2' gem 'spring-watcher-listen', '2.0.1' end group :production do gem 'pg', '0.20.0' end # Windows環境ではtzinfo-dataというgemを含める必要があります gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]「sqlite3 gem」は開発環境用のDBのGemですが、「heroku」は本番用のサーバーなので、「sqlite3」だとデプロイに失敗してしまいます。なので、「development」では「sqlite3 gem」ですが、「production」では「pg gem」(PostgreSQL)という本番環境用のものを使います。
Gemfileを書き換えた後、bundle installを実行する $ bundle install
ローカルの場合は、本番環境で使わないGemは入れないようにbundle install --without productionを実行する $ bundle install --without production production環境で行うGemは開発環境で使わないからwithoutで外に置いといて、インストールしないようにする
Gitを通してHeroku上にデプロイするため、Gitにコミット(セーブ)する必要があります $ git add Gemfile Gemfile.lock 先程、bundle installを実行した結果、”$ git status”で確認すると、GemfileとGemfile.lockはmodifiedになっているため、git addで追加します。
またエディタが出てきて面倒なため、「git commit」に「-m(message)」を追加すれば一行で終わります $ git commit -m "Add pg gem" git commit -m "Add pg gem"を実行した後、commitしましたというエディタ画面が出ますが、「J」と「K」で上に行ったり、下に行ったりして、「Q」で終了できます。
herokuにログインする $ heroku login --interactive
パスワード入力をスキップするための秘密鍵 $ heroku keys:add ? Would you like to upload it to Heroku? (Y/n)と出てきたら「Y」を入力します。
本番用のサーバーを作ります $ heroku create Creating app... done, ⬢ afternoon-sea-23202 https://afternoon-sea-23202.herokuapp.com/ | https://git.heroku.com/afternoon-sea-23202.githeroku createを実行すると、2つのURLが出てきて、左側のURLをクリックしてOpenを選択すると開けます。
git remote -vを実行すると、先程作ったサーバーにソースコードを送るためのショートカット(エイリアス)、Herokuができます $ git remote -v herokuという送り先を入力すると、サーバーにソースコードが送れます。
今いるmasterというブランチのソースコードをherokuに送る(git push) $ git push heroku master これで先程のURLをリロードすると「hello world!」が表示されます。
1.5.4 Herokuコマンド
アプリケーションの名前を変更 $ heroku rename rails-tutorial-kyotot Railsガイド
この記事はYassLabさんの解説動画を参考にさせていただきました。とてもわかりやすく、Railsチュートリアルに心折れそうな方にすごくオススメです!
- 投稿日:2019-12-24T16:30:38+09:00
ドロップダウン機能を有効にするため、Railsのapplication.jsファイルを通して、Bootstrapに同梱されているJavaScriptライブラリとjQueryを読み込むようアセットパイプラインに指示
railsチュートリアルで、Accountをクリックしてプルダウンメニュー出すために、少し手こずったのでメモ。
実装させたい機能⬇️
application.jsに2つのライブラリを追加。application.js//= require rails-ujs //= require jquery ⇦追加 //= require bootstrap ⇦追加 //= require turbolinks //= require_tree .
と、Sprockets::FileNotFoundエラーが出る。Gemfileにgemを追加。
gem 'rails', '~> 5.2.4', '>= 5.2.4.1' gem 'bcrypt', '3.1.12' gem 'bootstrap-sass', '3.3.7' gem 'jquery-rails', '~> 4.3', '>= 4.3.1' ⇦追加
- 投稿日:2019-12-24T16:26:27+09:00
Railsで作成したアプリケーションにRspec(E2Eテスト)実装してみた
はじめに
本記事は、Railsのテストフレームワーク(Rspec)の手順をまとめたものになります。
また、当記事は以前の投稿記事「Railsでセルフパスワード変更ページを作ってみた」で作成した環境を前提に作成していますので、参考にされる方はご注意ください。前提条件
- 開発環境はCloud9
- Linuxコマンドの使い方がわかる程度の力量
- Web開発は初学者クラスの力量(ruby on rails開発未経験/Progateのレッスンは修了済)
- Rspec初学者クラスの力量
参考記事
RSpecとCapybaraを使ってE2Eテストの土台を作ってみる
RailsでRSpecの初期設定を行う際のテンプレートを作ってみる
Rspecの設定はrails_helper.rbにだけ書けばよい
RSpecの初期設定メモ
Railsでrspecを使うように設定する
実用的な新機能が盛りだくさん!RSpec 3.3 完全ガイド
RSpec 設定
RSpecコトハジメ ~初期設定マニュアル~
使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」
既存のRailsプロジェクトをRSpec 3.0にアップグレードする際の注意点 ~RSpec 3は怖くないよ!~
特定のSpecでだけトランザクションのロールバックを無効にする
rspec-rails 3.7の新機能!System Specを使ってみた
SeleniumからHeadless Chromeを使ってみた
Amazon LinuxでSelenium環境を最短で構築する
RSpecの(describe/context/example/it)の使い分け
RSpec の letとlet!とbeforeの挙動と実行される順番
Ruby on Rails アプリケーションにおけるモンキーパッチの当て方【外部サイト】
rails generate rspec:install時に生成されるhelperの設定【外部サイト】
RailsでのRSpec実行時乱数の状態を実行毎に変わらないように固定する【外部サイト】
Rspec入門編ーテストコードを書いてみよう!ー【外部サイト】
RSpec 3 時代の設定ファイル rails_helper.rb について【外部サイト】
RSpec 3.5 から shared_context の使い方が少し変わっていた [RSpec]【外部サイト】
RSpecのshared_contextで共通処理を1ヶ所にまとめる【外部サイト】必要ソフトウェア
- ブラウザエミュレート環境インストール
- chrome(ブラウザ)
- GConf2(chromedriver用)
- chromedriver
- gemのインストール
- rspec-rails
- webdrivers
- selenium-webdriver
- capybara
ディレクトリ構成
今回のプロジェクトでは下記ディレクトリ構成となります。
本プロジェクトのディレクトリ構成spec ├── controllers │ └── users_password_controller_spec.rb ├── helpers │ └── capybara.rb ├── lib │ └── ssh_interactive_spec.rb ├── rails_helper.rb ├── spec_helper.rb └── system └── users_password_spec.rb
Selenium環境構築
chromeインストール
現在の環境がyumで新しいchromeがインストールできないので別の方法でインストールを実施します。
chromeインストール$ sudo curl https://intoli.com/install-google-chrome.sh | bash % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 9526 100 9526 0 0 14260 0 --:--:-- --:--:-- --:--:-- 14239 Working in /tmp/google-chrome-installation 【以下省略】 Extracting graphite2... Successfully installed google-chrome-stable, Google Chrome 78.0.3904.108 .余談ですが、chromeをアンインストールしたくなりましたら下記コマンド実行してください。
chromeアンインストール$ sudo yum --setopt=tsflags=noscripts -y remove google-chrome-stable $ sudo rm -rf /opt/google/chrome/chromedriverインストール
chromeインストール$ sudo yum -y install GConf2 Loaded plugins: priorities, update-motd, upgrade-helper You need to be root to perform this command. 【以下省略】 Installed: GConf2.x86_64 0:2.28.0-7.el6 Dependency Installed: ConsoleKit.x86_64 0:0.4.1-6.el6 ConsoleKit-libs.x86_64 0:0.4.1-6.el6 ORBit2.x86_64 0:2.14.17-7.el6 dbus-glib.x86_64 0:0.86-6.10.amzn1 eggdbus.x86_64 0:0.6-3.el6 libIDL.x86_64 0:0.8.13-2.1.4.amzn1 polkit.x86_64 0:0.96-11.el6_10.1 sgml-common.noarch 0:0.6.3-33.5.amzn1 Complete!gemインストール
Gemfileへ設定を追記します。
Gemfile追記$ cd PasswordChange $ vim Gemfile 【変更前】 group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] end 【変更後】 group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] gem 'selenium-webdriver' gem 'webdrivers' gem 'capybara' gem 'rspec-rails' endbundleインストールを実行します。
bundleインストール$ bundle installRspecとCapybara設定の追加
Rails用のRspec初期ファイルを作成します。
Rspec初期ファイル作成$ bundle exec rails g rspec:install create .rspec create spec/spec_helper.rb create spec/rails_helper.rb余談ですが、railsに組み込まないで使用する場合、下記コマンドでRspec初期ファイルを作成できます。
普通のRspec初期ファイル作成$ bundle exec rspec --init create .rspec create spec/spec_helper.rbCapybara用のファイルを作成します。
Capybaraファイル作成$ mkdir -p spec/helpers $ touch spec/helpers/capybara.rbCapybaraの設定を追加します。
Capybara設定追加(capybara.rb)require 'capybara/rspec' require 'selenium-webdriver' RSpec.configure do |config| config.include Capybara::DSL # javascript無 config.before(:each, type: :system) do driven_by :rack_test end # javascript有 config.before(:each, type: :system, js: true) do driven_by :selenium_chrome_headless, screen_size: [1280, 800], options: { browser: :chrome } do |driver_option| # Chrome オプション追加設定 driver_option.add_argument('disable-notifications') driver_option.add_argument('disable-translate') driver_option.add_argument('disable-extensions') driver_option.add_argument('disable-infobars') driver_option.add_argument('disable-gpu') driver_option.add_argument('no-sandbox') driver_option.add_argument('lang=ja') driver_option.add_argument('headless') end # Capybara設定 Capybara.javascript_driver = :selenium_chrome_headless Capybara.run_server = true Capybara.default_selector = :css Capybara.default_max_wait_time = 5 Capybara.ignore_hidden_elements = true end end次に「rails_helper.rb」「spec_helper.rb」の設定を変更します。
rails_helper.rbの設定変更【コメント部分は全部削除しています】 require 'spec_helper' ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../config/environment', __dir__) abort("The Rails environment is running in production mode!") if Rails.env.production? require 'rspec/rails' begin ActiveRecord::Migration.maintain_test_schema! rescue ActiveRecord::PendingMigrationError => e puts e.to_s.strip exit 1 end RSpec.configure do |config| config.fixture_path = "#{::Rails.root}/spec/fixtures" config.use_transactional_fixtures = true config.infer_spec_type_from_file_location! config.filter_rails_from_backtrace! endspec_helper.rbの設定変更【コメント部分は全部削除しています】 require 'helpers/capybara.rb' RSpec.configure do |config| config.expect_with :rspec do |expectations| expectations.include_chain_clauses_in_custom_matcher_descriptions = true end config.mock_with :rspec do |mocks| mocks.verify_partial_doubles = true end config.shared_context_metadata_behavior = :apply_to_host_groups config.failure_color = :red config.fail_fast = false config.color = true config.formatter = :documentationテストコード記述
テストコード保管先とテストコード用のファイルを作成します。
ディレクトリとファイル作成$ mkdir -p spec/lib spec/system $ touch spec/lib/ssh_interactive_spec.rb spec/lib/users_password_spec.rb実際にテストコードを記述します。
自作モジュールテスト(Rspec)
SSH接続して対話式にコマンドを実行する機能のテストを実施します。
モジュールテスト(ssh_interactive_spec.rb)require 'rails_helper' require 'ssh_interactive' require 'settings' RSpec.describe SshInteractive do describe "SSH対話式(passwdコマンド)" do let!(:user_id) {'ユーザー名'} let!(:pass_old) {'現行パスワード'} let!(:pass_new) {'新しいパスワード'} let!(:host) {Settings.ssh_params.host} let!(:port) {Settings.ssh_params.port} let!(:keys) {Settings.ssh_params.keys} let!(:passphrase) {Settings.ssh_params.passphrase} let!(:ssh) { SshInteractive.new } context 'SSH接続先の情報が誤っている場合' do # 前処理 before do ssh.set_host(host) ssh.set_port(22) ssh.set_publickey_auth(keys, passphrase) end it 'SSH接続エラーが発生する事' do result = ssh.password_change(user_id, pass_old, pass_new , pass_new) expect(result).to eq (-10) end end context '存在しないユーザーが設定されている場合' do # 前処理 before do ssh.set_host(host) ssh.set_port(port) ssh.set_publickey_auth(keys, passphrase) end it 'SSH接続エラーが発生する事' do result = ssh.password_change('test1', pass_old, pass_new , pass_new) expect(result).to eq (-1) end end context '旧(現行)パスワード間違っている場合' do # 前処理 before do ssh.set_host(host) ssh.set_port(port) ssh.set_publickey_auth(keys, passphrase) end it '認証エラーが発生する事' do result = ssh.password_change(user_id, pass_old + 'dgh', pass_new , pass_new) expect(result).to eq (-2) end end context '辞書攻撃チェックに引っかかった場合' do # 前処理 before do ssh.set_host(host) ssh.set_port(port) ssh.set_publickey_auth(keys, passphrase) end it 'パスワード変更エラーが発生する事' do result = ssh.password_change(user_id, pass_old, pass_new + 'abcdefghijk' , pass_new + 'abcdefghijk') expect(result).to eq (-3) end end context '全ての条件がクリアされている場合' do # 前処理 before do ssh.set_host(host) ssh.set_port(port) ssh.set_publickey_auth(keys, passphrase) end it 'パスワード変更処理が正常に完了する事' do result = ssh.password_change(user_id, pass_old, pass_new, pass_new) expect(result).to eq (0) end end end endE2Eテスト(System Spec + Selenium)
Webアプリケーション(画面)のテストを実施します。
E2Eテスト(users_password_spec.rb)require 'rails_helper' RSpec.describe 'top_form', type: :system, js: true do describe '/top' do let!(:save_path) {'tmp/screenshots/'} let!(:user_id) {'test'} let!(:pass_old) {'_8Ac-E5s'} let!(:pass_new) {'X9a@ywV5'} before do visit '/top' end context '存在しないユーザーが入力されている場合' do before do find('#user_id').set(user_id + '1') find('#password_old').set(pass_old) find('#password_new').set(pass_new) find('#password_verify').set(pass_new) find_button('変更').click end it '警告メッセージが表示される事' do expect(page).to have_content "ユーザーID又はパスワードが間違っています" end after do page.driver.save_screenshot(save_path + 'page_top_case01.png') end end context '旧(現行)パスワード間違っている場合' do before do find('#user_id').set(user_id) find('#password_old').set(pass_old + 'abc') find('#password_new').set(pass_new) find('#password_verify').set(pass_new) find_button('変更').click end it "警告メッセージが表示される事" do expect(page).to have_content "パスワードが間違っています" end after do page.driver.save_screenshot(save_path + 'page_top_case02.png') end end context '旧(現行)パスワードと新しいパスワードが一致する場合' do before do find('#user_id').set(user_id) find('#password_old').set(pass_old) find('#password_new').set(pass_old) find('#password_verify').set(pass_new) find_button('変更').click end it "警告メッセージが表示される事" do expect(page).to have_content "古いパスワードと新しいパスワードが同じです" end after do page.driver.save_screenshot(save_path + 'page_top_case03.png') end end context '新しいパスワードと新しいパスワード(確認)が一致しない場合' do before do find('#user_id').set(user_id) find('#password_old').set(pass_old) find('#password_new').set(pass_new) find('#password_verify').set(pass_new + 'abc' ) find_button('変更').click end it '警告メッセージが表示される事' do expect(page).to have_content "新しいパスワードと新しいパスワード" end after do page.driver.save_screenshot(save_path + 'page_top_case04.png') end end context '新しいパスワードが複雑性を満たすパスワードではない場合(数字無)' do before do find('#user_id').set(user_id) find('#password_old').set(pass_old) find('#password_new').set('Xda@ywVb') find('#password_verify').set('Xda@ywVb') find_button('変更').click end it '警告メッセージが表示される事' do expect(page).to have_content "複雑性を満たすパスワードになっていません" end after do page.driver.save_screenshot(save_path + 'page_top_case05.png') end end context '新しいパスワードが複雑性を満たすパスワードではない場合(英小文字無)' do before do find('#user_id').set(user_id) find('#password_old').set(pass_old) find('#password_new').set('X9A@YWV5') find('#password_verify').set('X9A@YWV5') find_button('変更').click end it '警告メッセージが表示される事' do expect(page).to have_content "複雑性を満たすパスワードになっていません" end after do page.driver.save_screenshot(save_path + 'page_top_case06.png') end end context '新しいパスワードが複雑性を満たすパスワードではない場合(英大文字無)' do before do find('#user_id').set(user_id) find('#password_old').set(pass_old) find('#password_new').set('x9a@ywwv5') find('#password_verify').set('x9a@ywwv5') find_button('変更').click end it '警告メッセージが表示される事' do expect(page).to have_content "複雑性を満たすパスワードになっていません" end after do page.driver.save_screenshot(save_path + 'page_top_case07.png') end end context '新しいパスワードが複雑性を満たすパスワードではない場合(対象記号無)' do before do find('#user_id').set(user_id) find('#password_old').set(pass_old) find('#password_new').set('X9aywV5') find('#password_verify').set('X9aywV5') find_button('変更').click end it '警告メッセージが表示される事' do expect(page).to have_content "複雑性を満たすパスワードになっていません" end after do page.driver.save_screenshot(save_path + 'page_top_case08.png') end end context '新しいパスワードの文字数が基準を満たしていない場合(7文字以下)' do before do find('#user_id').set(user_id) find('#password_old').set(pass_old) find('#password_new').set('X9a@ywV') find('#password_verify').set('X9a@ywV') find_button('変更').click end it '警告メッセージが表示される事' do expect(page).to have_content "パスワードの文字数が基準を満たしていません" end after do page.driver.save_screenshot(save_path + 'page_top_case09.png') end end context '新しいパスワード内にユーザー名が含まれている場合' do before do find('#user_id').set(user_id) find('#password_old').set(pass_old) find('#password_new').set('X9a@ywV5az74d52_test') find('#password_verify').set('X9a@ywV5az74d52_test') find_button('変更').click end it '警告メッセージが表示される事' do expect(page).to have_content "新しいパスワードにユーザーIDと同じ文字列が含まれています" end after do page.driver.save_screenshot(save_path + 'page_top_case10.png') end end context 'パスワード内に辞書攻撃に該当する文字列が含まれいている場合' do before do find('#user_id').set(user_id) find('#password_old').set(pass_old) find('#password_new').set(pass_new + 'abcdefghijk') find('#password_verify').set(pass_new + 'abcdefghijk') find_button('変更').click end it '警告メッセージが表示される事' do expect(page).to have_content "辞書攻撃チェックに該当します" end after do page.driver.save_screenshot(save_path + 'page_top_case11.png') end end context '全ての条件がクリアされている場合' do before do find('#user_id').set(user_id) find('#password_old').set(pass_old) find('#password_new').set(pass_new) find('#password_verify').set(pass_new) find_button('変更').click end it 'パスワード変更完了画面が表示される事' do expect(page).to have_content "パスワード変更が完了しました" end after do page.driver.save_screenshot(save_path + 'page_top_normal.png') end end end endテスト実行
最後に作成したテストツールを使用してテストを実行してみます。
※エラー結果も見せたいため、あえてエラーを発生させています。
(嘘です。公開用の設定に変更したため環境周りでエラーが出ています)テストコマンド実行(ssh_interactive_spec.rbのみ)$ bundle exec rspec spec/lib/ssh_interactive_spec.rb SshInteractive SSH対話式(passwdコマンド) SSH接続先の情報が誤っている場合 SSH接続エラーが発生する事 存在しないユーザーが設定されている場合 SSH接続エラーが発生する事 旧(現行)パスワード間違っている場合 認証エラーが発生する事 (FAILED - 1) 辞書攻撃チェックに引っかかった場合 パスワード変更エラーが発生する事 (FAILED - 2) 全ての条件がクリアされている場合 パスワード変更処理が正常に完了する事 (FAILED - 3) Failures: 1) SshInteractive SSH対話式(passwdコマンド) 旧(現行)パスワード間違っている場合 認証エラーが発生する事 Failure/Error: expect(result).to eq (-2) expected: -2 got: -1 (compared using ==) # ./spec/lib/ssh_interactive_spec.rb:59:in `block (4 levels) in <top (required)>' 2) SshInteractive SSH対話式(passwdコマンド) 辞書攻撃チェックに引っかかった場合 パスワード変更エラーが発生する事 Failure/Error: expect(result).to eq (-3) expected: -3 got: -1 (compared using ==) # ./spec/lib/ssh_interactive_spec.rb:74:in `block (4 levels) in <top (required)>' 3) SshInteractive SSH対話式(passwdコマンド) 全ての条件がクリアされている場合 パスワード変更処理が正常に完了する事 Failure/Error: expect(result).to eq (0) expected: 0 got: -1 (compared using ==) # ./spec/lib/ssh_interactive_spec.rb:90:in `block (4 levels) in <top (required)>' Finished in 0.32911 seconds (files took 2.54 seconds to load) 5 examples, 3 failuresここまでが今回の記事におけるRspec実装の内容になります。
下記以降はRspec実装に当たっての各種参考設定の情報になります。【参考設定情報】
下記ディレクトリ構成は公式ドキュメント RSpec Rails 3-9 【外部サイト】からの引用となります。
作成時点では、このディレクトリ構造を知らなかったのであまり守られていないです。
ディレクトリ構成
app ├── controllers │ ├── application_controller.rb │ └── books_controller.rb ├── helpers │ ├── application_helper.rb │ └── books_helper.rb ├── models │ ├── author.rb │ ├── book.rb └── views ├── books ├── layouts lib ├── country_map.rb ├── development_mail_interceptor.rb ├── enviroment_mail_interceptor.rb └── tasks ├── irc.rake spec ├── controllers │ ├── books_controller_spec.rb ├── country_map_spec.rb ├── features │ ├── tracking_book_delivery_spec.rb ├── helpers │ └── books_helper_spec.rb ├── models │ ├── author_spec.rb │ ├── book_spec.rb ├── rails_helper.rb ├── requests │ ├── books_spec.rb ├── routing │ └── books_routing_spec.rb ├── spec_helper.rb └── tasks │ ├── irc_spec.rb └── views ├── books設定ファイルのメモ
spec_helper.rbの設定内容
Rspecに関する設定を書くための設定ファイルらしいです。
config設定 概要 config.filter_run_when_matching :focus 「rspec --tag focus」コマンド実行時に「:focus」タグが設定されたテストを実行。 config.run_all_when_everything_filtered = true 「:focus」タグがついたものが何もない場合、フィルタを無視。 config.example_status_persistence_file_path = "spec/examples.txt" 「rspec --only-failures」コマンド実行時にテスト結果の一時保管。 config.disable_monkey_patching! モンキーパッチを無効化。 config.profile_examples = 10 実行後、遅いテスト項目を表示(10件)。 config.order = :random 実行結果の順番(random = ランダム) Kernel.srand config.seed 「rspec --seed 【seed値】」コマンド実行時にランダム結果を固定。 config.failure_color = :red 実行結果のエラー内容をカラー出力(red = 赤色) config.fail_fast = false 実行中にエラーが発生時の処理(false = 最後までテスト実施) config.color = true 実行結果の標準出力をカラー出力 config.formatter = :documentation 実行結果のフォーマット指定(デフォルト:progress) config.shared_context_metadata_behavior = :apply_to_host_groups shared_contextの記述指定(:trigger_inclusionは3.4以前の書き方)。 rails_helper.rbの設定内容
Rails特有の設定を書くための設定ファイルらしいです。
config設定 概要 config.infer_spec_type_from_file_location! specファイルが配置されているディレクトリ内のspecタイプ(model, controller, feature等)を自動判別。 config.filter_rails_from_backtrace! backtrace表示を簡素化。 config.use_transactional_fixtures = true テスト実行後、データベースのデータ削除。 Capybaraの設定内容
RubyでWebアプリケーションのE2Eテストフレームワークを提供する機能らしいです。
capybara.rb設定 概要 config.include Capybara::DSL Capybaraを取り込むのに必要な設定。 driven_by :rack_test rackアプリケーションをテストするための機能。javascriptのテスト不可。 driven_by :selenium_chrome_headless Rubyクライアントライブラリを使用してChromeブラウザでテストするための機能。javascriptのテスト可。 driver_option.add_argument('disable-notifications') Web通知やPush APIによる通知を無視。 driver_option.add_argument('disable-translate') 翻訳ツールバーを無効。 driver_option.add_argument('disable-extensions') 拡張機能を無効。 driver_option.add_argument('disable-infobars') インフォバーの表示を無効。 driver_option.add_argument('disable-gpu') GPU描画処理を無効。 driver_option.add_argument('no-sandbox') sandboxを無効。 driver_option.add_argument('lang=ja') 言語を日本語。 driver_option.add_argument('headless') Headlessモード(画面を表示せずに動作)を有効。 Capybara.javascript_driver = :selenium_chrome_headless chromeのheadlessモードでjavascript実行。 Capybara.run_server = true rackサーバーを実行。 Capybara.default_selector = :css セレクタ(find等)利用時の設定。 Capybara.default_max_wait_time = 5 ajaxやcss等の待ち時間。 Capybara.ignore_hidden_elements = true 非表示要素(display:none)は検出しない。 おわりに
テストツール自体はそれほど難しくありませんでしたが、環境や設定周りがすごく面倒でした。
また、今回はモデルを利用していなかったため「テストデータ作成」関連は手を付けていないため、
どこかで調査して記事にしていこうかなと考えています。ただ、今回の記事では全てを理解したわけではないので、今後も気づいたことがあれば加筆・修正していく予定です。
- 投稿日:2019-12-24T15:15:22+09:00
rails 『アウトプット』
application.scssがおかしいと思い@importの順番を変えたが反映されず名前を変えてもエラーが出なかったためapplication.html.hamlを見たら配置ミスがあった。application.html.hamlはhaml,scss両方を統合しているため確かめる必要がある。
- 投稿日:2019-12-24T14:27:24+09:00
Rubyでじゃんけん
Rubyにてじゃんけん (Rock-Paper-Scissors)
1.じゃんけんの構造化
そもそもじゃんけんとは??
グー、チョキ、パーのどれかを互いに選択し、勝敗を決めるものである。
今回はプレイヤーとプログラムで戦う。
グー Rock
チョキ Paper
パー Scissors
で対戦。
(プログラムでは、乱数を返す、randが活躍するが、それは後々)2.playerの手選択
入力による代入
p 'rock-paper-scissors' player=gets↑rock、paper、scissorsのどれかを選んでもらう
プレイヤーがスペルミスしたときの対応も重要になる3.programの手選択
乱数で決定する。
program_hand=rand(3)↑program_handは0、1、2を乱数で返すことになる。
4.整理
人間にとっては、Rock-Paper-Scissorsという文字表記が分かりやすいが、乱数を0、1、2でやったように、プログラム(機械)からすると、文字よりも数字が分かりやすい。
よって、じゃんけんをしている2人(player、program)において、手を数字と文字であらわしておく
0 グー Rock
1 チョキ Paper
2 パー Scissorsif program_hand==0 program="rock" elsif program_hand==1 program="paper" elsif program_hand==2 program="scissors" endif player=="rock" player_hand=0 elsif player=="paper" player_hand=1 elsif player=="scissors" player_hand=2 end5.じゃんけんの勝敗をつける
ここで、条件分岐が登場
if文を用いるif player_hand==program_hand p 'draw' elsif ((player_hand==0 and program_hand==2) or (player_hand==1 and program_hand==0) or (player_hand==2 or program_hand==0))and a==1 p 'you win' elsif p 'you lose' end条件の式を書く上で、論理が連続で出てくることがある。(andやor)
論理の優位性などという話があるらしいが、難しい。
先に計算したい方をかっこでくくっちゃうのが早い、簡単、わかりやすい。6.スペルミスに対する対応
rock、paper、scissorsのどれかを選んでもらうことになってるが、
それ以外の入力には、いかにして対応するのか??one more please
と表示させることにaという変数を登場させる。初期値0
rock、paper、scissorsのどれかが記入されたときのみa=1を代入。aをつかって判断する
↓完成
p 'rock-paper-scissors' player=gets a=0 if player=="rock" player_hand=0 a=1 elsif player=="paper" player_hand=1 a=1 elsif player=="scissors" player_hand=2 a=1 end program_hand=rand(3) if program_hand==0 program="rock" elsif program_hand==1 program="paper" elsif program_hand==2 program="scissors" end if a==0 p 'one more please' elsif player_hand==program_hand p 'draw' elsif ((player_hand==0 and program_hand==2) or (player_hand==1 and program_hand==0) or (player_hand==2 or program_hand==0))and a==1 p 'you win' elsif p 'you lose' end p "player",player,"program",program
- 投稿日:2019-12-24T13:51:02+09:00
railsにてcreated_at の曜日も日本語で表示する方法( lメソッド)
- 目標①:railsにて、DBからcreated_at の日時を表示(取得)したい。
変数.html.erb<%= link_to(変数.created_at.strftime('%Y年/%m月/%d日(%a)%H:%M')," リンク先を記入") %>
まず試したコード
(今回は、更にlink_toを使いたい)
- 「 XXXX年 XX月XX日(mon) XX:XX 」とまで成功。
- <%= 変数.created_at.strftime('%Y/%m/%d(%a)%H:%M') %> を実行し、曜日が英語標記のままのため苦戦。
- #{%w(日 月 火 水 木 金 土… 系も試したが、上手くいかない。
- config/initiallzers/time_formats.rb等も作成し定義を試したが、上手くいかない。
ーーーーーーーーーーーーーーーーーーーーー
<備忘録>
目標② (mon)ではなく、曜日も日本語で表示する簡単な方法①gem 'i18n_generators' をbundleインストール
②en.ymlと同じ階層ディレクトリにconfig/locales/ja.yml を作成。以下のコマンド実行すると内容が書き換わり大変便利。
rails g i18n_locale ja③ja.yml 内の
time: default: "%Y年%m月%d日(%a) %H時%M分" ←ここを取得したいように自分で変更する④表示したいhtml.erb内へ lメソッドにて記入。
<%= link_to(l(テーブル名.created_at),"リンク先") %>リンクではない場合、↓
<%= l テーブル名.created_at %>念のため、サーバーを立ち上げ直し、
曜日も (mon)→(月) 日本語になりました。最終参考
Railsで日時をフォーマットするときはstrftimeよりも、lメソッドを使おう @Junichi Ito
*個人勉強用サイト作成のため、
曜日を表示しないで妥協するか迷いましたが、諦めないで試行錯誤することが大切だと感じています。
- 投稿日:2019-12-24T12:30:49+09:00
Ruby数字出力 0埋め
Rubyでの計算結果を0埋めで表示(3→003)
今回、例として、入力された数値の倍の数を三桁の0埋めで考える。
1→002
2→004
6→0121.まず普通の表示
1→2
2→4
6→12
をまず、できるようにするputs "数字を入れてください" number = gets.to_i number2=number*2 print number2↑これで、完了。
2.0埋めを考えていく
sprintf("%03d",number)や
"%03d" % numberが、numberの0埋め3桁表示となる。
実行するプログラム
puts "数字を入れてください" number = gets.to_i number2=number*2 number3=sprintf("%03d", number2) print number31→002
2→004
6→012が確認できた
参考文献
- 投稿日:2019-12-24T11:57:20+09:00
Ruby計算 気を付けるべきポイント(変数の入力)
Rubyにて変数に文字列代入の際のポイント
今回行いたいのは、Rubyにて数値の計算。
(例) 入力した数値の倍の値を表示するプログラム。(2→4)1.まず入力を考える。
変数に数を代入する。
代入は、入力によって行う。
入力の際は、getコマンドを用いるnunber=getsこれで入力を行えば、代入される。
2.計算を考える。
今回は倍にするという単純な計算。
*が×を表す。number2=number*23.結果表示
計算結果はnumber2に入ってるので、
print number2とすると、結果は.....
2 2あれ??
文字列として、代入されてたことが発覚
.to_iをつけて、文字列→数値にする必要ありnumber = gets.to_i number2=number*2 print number2これで表示は
4となった。
4.参考ブログ
- 投稿日:2019-12-24T10:01:36+09:00
サンプルコードでわかる!Ruby 2.7の主な新機能と変更点 Part 3 - 新機能と変更点の総まとめ
はじめに
Rubyは毎年12月25日にアップデートされます。
Ruby 2.7については2019年12月21日にrc2がリリースされました。この記事ではRuby 2.7で導入される変更点や新機能について、サンプルコード付きでできるだけわかりやすく紹介していきます。
ただし、Ruby 2.7は多くの新機能や変更点があり、1つの記事に収まらないのでいくつかの記事に分けて書いていきます。
番号指定パラメータ、パターンマッチ構文、キーワード引数に関する仕様変更についてはすでに他の記事で説明したので、本記事ではそれ以外の変更点を説明しています。すでに説明したRuby 2.7の新機能や変更点はこちら
Ruby 2.7の新機能や変更点は非常に多いので、いくつかの記事に分けて説明しています。
以下の記事はすでに公開済みです。
- サンプルコードでわかる!Ruby 2.7の主な新機能と変更点 Part 1 - 番号指定パラメータ(numbered parameter) - Qiita
- サンプルコードでわかる!Ruby 2.7の新機能・パターンマッチ(前編) - Qiita
- サンプルコードでわかる!Ruby 2.7の新機能・パターンマッチ(後編) - Qiita
- サンプルコードでわかる!Ruby 2.7の主な新機能と変更点 Part 2 - キーワード引数に関する仕様変更 - Qiita
本記事の情報源
本記事は以下のような情報源をベースにして、記事を執筆しています。
- Ruby 2.7.0-rc2 リリース
- rc2のNEWSページ
- リリースノートやNEWSに記載されている各種issue
また、issueを追いかけてもピンと来なかった内容については、ブログ「ruby trunk changes」のコミット解説を参考にさせてもらいました(nagachikaさん、どうもありがとうございます!)。
動作確認したRubyのバージョン
本記事は以下の環境で実行した結果を記載しています。
$ ruby -v ruby 2.7.0rc2 (2019-12-22 master 75acbd5f00) [x86_64-darwin19]フィードバックお待ちしています
本文の説明内容に間違いや不十分な点があった場合は、コメント欄や編集リクエスト等で指摘 or 修正をお願いします?
それでは以下が本編です!
構文や言語機能上の変更点
パターンマッチ構文が試験的に導入された(別記事にて説明済み)
Ruby 2.7ではパターンマッチ(またはパターンマッチング)構文が試験的に導入されました。
関数型言語で広く使われているパターンマッチという機能が実験的に導入されました。 渡されたオブジェクトの構造がパターンと一致するかどうかを調べ、一致した場合にその値を変数に代入するといったことができるようになります。
以下はパターンマッチ構文の使用例です。
case {status: :error, message: 'User not found.'} in {status: :success} puts "Success!" in {status: :error, message: message} puts "Error: #{message}" end #=> Error: User not found.パターンマッチについては以下の記事で詳しく説明しています。
キーワード引数と普通の引数の自動変換が非推奨になった(別記事にて説明済み)
Ruby 2.7ではキーワード引数と普通の引数の自動変換が非推奨になり、警告が発生します。
以下は警告が発生するようになったコードの例です。
def foo(key: 0) p key end foo({key: 42}) #=> The last argument is used as the keyword parameter #=> 42警告が発生するコードはRuby 3で動かなくなります。
詳しい内容はこちらの記事をご覧ください。
ブロックの仮引数として番号指定パラメータが試験的に導入された(別記事にて説明済み)
Ruby 2.7ではブロックの仮引数として番号指定パラメータ(numbered parameter)が試験的に導入されました。
これにより、|s|
のように明示的に引数名を指定する代わりに、_1
のような連番でブロックの仮引数を受け取ることができます。# 番号指定パラメータを使わない場合 %w(1 20 300).map { |s| s.rjust(3, '0') } #=> ["001", "020", "300"] # 番号指定パラメータを使う場合 %w(1 20 300).map { _1.rjust(3, '0') } #=> ["001", "020", "300"]# 番号指定パラメータを使わない場合 [1, 2, 3, 4].inject(0) { |memo, n| memo + n } #=> 10 # 番号指定パラメータを使う場合 [1, 2, 3, 4].inject(0) { _1 + _2 } #=> 10詳しい内容はこちらの記事をご覧ください。
メソッドの内のブロックを伴わないProc.new/procとlambdaが警告またはエラー扱いとなった
Ruby 2.6まではメソッド内でブロックなしのProc.new/procやlambdaを呼ぶと、暗黙的にメソッド呼び出し時に引き渡したブロックが割り当てられていました。
def proc_without_block # このprocにはこのメソッドを呼びだしたときに # 引き渡したブロックが暗黙的に割り当てられる proc.call * 100 # Proc.newを使った場合も同様 # Proc.new.call * 100 end proc_without_block { 123 } #=> 12300def lambda_without_block # lambdaの場合も同様 lambda.call * 100 end lambda_without_block { 123 } #=> 12300Ruby 2.7ではProc.new/procのときに警告が、lambdaのときは例外が発生するようになりました。
def proc_without_block proc.call * 100 end # Ruby 2.7では警告が出る proc_without_block { 123 } #=> warning: Capturing the given block using Kernel#proc is deprecated; use `&block` insteaddef lambda_without_block lambda.call * 100 end # Ruby 2.7では例外が発生する lambda_without_block { 123 } #=> ArgumentError (tried to create Proc object without a block)開始値省略範囲式(beginless range)が試験的に導入された
Ruby 2.7では開始値省略範囲式(beginless range)が試験的に導入されました。
numbers = [10, 20, 30, 40, 50] # beginless rangeを使って配列の最初の3要素を取得する numbers[..2] #=> [10, 20, 30] # 従来通り次のように書いても同じ numbers[0..2] #=> [10, 20, 30]Ruby 2.6で導入されたendless rangeと組み合わせると次のようなコードも書けます。
n = -5 ret = case n when ..-1 'minus' when 1.. 'plus' else 'zero' end ret #=> minus特殊変数の
$;
と$,
に非nil値を設定すると警告が出るようになったRuby 2.7ではsplitメソッドのデフォルトの区切り文字を表す特殊変数
$;
と、デフォルトの出力フィールド区切り文字列(joinメソッドなどで使われる)を表す$,
で、nil以外の値を設定すると警告が出るようになりました。
また、その状態でsplitメソッドやjoinメソッドを呼び出した際も警告が出ます。# デフォルト値はnil $; #=> nil # 引数なしでsplitメソッドを呼ぶと空白文字で分割される 'ab,c d'.split #=> ["ab,c", "d"] # 非nil値を設定すると警告が出る $; = ',' #=> warning: non-nil $; will be deprecated # splitメソッドを呼んだときも警告が出る 'ab,c d'.split #=> ["ab", "cd"] #=> warning: $; is set to non-nil value# デフォルト値はnil $, #=> nil # 引数なしでjoinメソッドを呼ぶと空文字で連結される %w(a b c).join #=> "abc" # 非nil値を設定すると警告が出る $, = '-' #=> warning: non-nil $, will be deprecated # joinメソッドを呼んだときも警告が出る %w(a b c).join #=> "a-b-c" #=> warning: $, is set to non-nil valueヒアドキュメント識別子に付ける
'
や"
が同じ行にないと構文エラーが出るようになった下のコードのようにヒアドキュメントの識別子に付ける
'
や"
が同じ行にない場合、Ruby 2.6までは警告が出ていましたが、Ruby 2.7からはsyntax errorが発生するようになりました。s = <<~"TEXT " Hello! TEXT #=> Ruby 2.6では以下の警告が発生する # warning: here document identifier ends with a newline #=> Ruby 2.7ではsyntax errorが発生する # unterminated here document identifier # s = <<~"TEXT # syntax error, unexpected end-of-input # s = <<~"TEXTフリップフロップ構文の警告が撤回された
Ruby 2.6からフリップフロップ構文を使うと警告が発生するようになっていましたが、Ruby 2.7では(正確にはRuby 2.6.4以降では)この変更が撤回され、警告が出なくなりました。
numbers = 1..10 ret = numbers.map do |n| # この下の行がフリップフロップ構文 if (n % 3 == 0)..(n % 2 == 0) n * 10 end end #=> Ruby 2.6.0〜2.6.3では以下の警告が発生していたが、 # Ruby 2.6.4以降とRuby 2.7では発生しなくなった # warning: flip-flop is deprecated ret.compact #=> [30, 40, 60, 90, 100]フリップフロップ構文って何?という方は以下の記事をご覧ください。
改行を伴うメソッドチェーンにコメント行を挟み込めるようになった
Ruby 2.7では、以下のように改行を伴うメソッドチェーンにコメント行を挟み込めるようになりました。
200 # 次の値(つまり201)を得る .then(&:succ) # 文字列に変換する .then(&:to_s) # 逆順にする .then(&:reverse) #=> "102"ドットが後に来るパターンでも大丈夫です。
200. # 2倍する then(&:succ). # 文字列に変換する then(&:to_s). # 逆順にする then(&:reverse)ちなみに、Ruby 2.6では以下のように構文エラーが発生していました。
syntax error, unexpected '.', expecting end-of-input .then(&:succ)privateメソッドをself付きで呼び出せるようになった
これまでRubyのprivateメソッドは「レシーバを指定して呼び出すことができないメソッド」とされてきました。
そのためクラス内でもself
付きでprivateメソッドを呼び出すと例外が発生していました。class Foo def hello # nameはprivateメソッドなのでself付きで呼び出すことはできない "I am #{self.name}" end private def name 'Alice' end end foo = Foo.new # self.nameと書いていたので、例外が発生する foo.hello #=> `hello': private method `name' called for #<Foo:0x00007fb0488b24c8> (NoMethodError)Ruby 2.7ではself付きでprivateメソッドを呼び出せるようになりました。
class Foo def hello # Ruby 2.7ではself付きでprivateメソッドを呼び出せる "I am #{self.name}" end private def name 'Alice' end end foo = Foo.new foo.hello #=> "I am Alice" # もちろん、クラスの外からはレシーバ付きでprivateメソッドを呼び出すことはできない foo.name #=> private method `name' called for #<Foo:0x00007f91049094e8> (NoMethodError)ちなみに、これまでのRubyでもセッターメソッドだけはprivateでも例外的にself付きで呼び出すことができていました。(こちらの記事も参考)
class Foo def initialize @name = 'Alice' end def hello "I am #{self.name}" end def change_name # Ruby 2.6以前でも、セッターメソッドだけはprivateでもself付きで呼び出せた self.name = 'Bob' end private def name=(value) @name = value end def name @name end end foo = Foo.new foo.hello #=> I am Alice foo.change_name foo.hello #=> I am Bob今回のRuby 2.7の変更により、セッターメソッドか、そうでないかに関わらず、一貫してprivateメソッドをself付きで呼び出せるようになります。
rescue修飾子を伴う多重代入の挙動が変わった
Ruby 2.6では以下のようにrescue修飾子と多重代入を行うと次のような結果になっていました。
# ZeroDivisionErrorが発生するが、rescueする a, b = [1/0, 2/0] rescue [10, 20] a #=> nil b #=> nilこれは次のようにパースされていたためです。
(a, b = [1/0, 2/0]) rescue [10, 20]Ruby 2.7ではパースのされ方が以下のように変更されました。
a, b = ([1/0, 2/0] rescue [10, 20])これにより、rescue修飾子から返される配列を多重代入できるようになりました。
a, b = [1/0, 2/0] rescue [10, 20] a #=> 10 b #=> 20特異クラス構文内でyieldを使うと警告が出るようになった
Ruby 2.7では、以下のように特異クラス構文内でyieldを呼び出すと警告が出るようになりました。(こんなコードを書くことは、まれだと思いますが・・・)
def yield_in_class_syntax class << Object.new yield end end yield_in_class_syntax { puts 'nihao!' } #=> warning: `yield' in class syntax will not be supported from Ruby 3.0. [Feature #15575] # nihao!全引数を別のメソッドに引き渡す
...
引数が導入されたRuby 2.7ではあらゆる引数を受け取って、別のメソッドに引き渡す
...
引数が導入されました。def add(a, b) a + b end def add_with_description(...) # 受け取った引数をすべてそのままaddメソッドに引き渡す answer = add(...) "answer is #{answer}" end add_with_description(2, 3) #=> answer is 5別のメソッドを呼び出すときは丸かっこ(
()
)が必須です。丸かっこがないと開始と終了のない範囲オブジェクト(nil...nil
)と見なされます。# (...)と書かなかった場合は、nil...nilをaddメソッドを渡したことになる(警告も出る) answer = add ... #=> warning: ... at EOL, should be parenthesized?メソッドの仮引数も別メソッドに渡す引数も、いずれも
...
になっている必要があります。
片方だけが通常の引数だったり、引数の一部だけが...
になっていたりすると、構文エラーになります。def add_with_description(a, b) # 仮引数は通常の引数で、別メソッドの呼び出しが...になっていると構文エラー answer = add(...) "answer is #{answer}" end# 引数の一部が通常の引数で、残りが...になっていると構文エラー def add_with_description(a, ...) answer = add(a, ...) "answer is #{answer}" endセキュリティモデル関連の特殊変数やメソッドのサポートが縮小された
RubyにはCGIプログラミングをサポートするためのセキュリティ機能(セキュリティモデル)が用意されています。(参考)
しかし、近年のエコモデルではこの仕組みはあまり役に立たないため、サポートが縮小されつつあります。特殊変数の
$SAFE
はRubyの「セーフレベル」を設定するために使われていましたが、Ruby 2.7ではこの変数を変更したり、参照したりすると警告が表示されます。# 参照すると警告が出る $SAFE #=> warning: $SAFE will become a normal global variable in Ruby 3.0 # 0 # 変更しても警告が出る $SAFE = 1 #=> warning: $SAFE will become a normal global variable in Ruby 3.0また、taint, untaint, trust, untrust といった、セキュリティモデルに関連するメソッドも無効化されています。(オブジェクトは汚染状態にはならない)
some = "puts '@&%&(#!'" some.tainted? #=> false # Ruby 2.7では呼びだしても何も変化がない some.taint some.tainted? #=> falseのままになる(Ruby 2.6ではtrueになる)シンタックスハイライトや自動インデントなど、irbが大きく進化した
Ruby 2.7ではirbが大きく進化しました。
進化その1) シンタックスハイライトされる
irb内に打ち込んだコードが自動的にシンタックスハイライトされます。
進化その2) 自動的にインデントしてくれる
進化その3) 上下キーで複数行の入力履歴をまとめて行き来できる
ただし、長いメソッドやif文を入力したあとだと、履歴をさかのぼるのがちょっと大変かもしれません。
進化その4) TABキーを押すと入力候補を表示してくれる
メソッドを途中まで入力してTABキーを押すとクラス名やメソッド名、変数名などの入力候補を表示してくれます。
進化その5) TABキーを2回押すとクラスやメソッドのドキュメントを表示してくれる
メソッド名を入力してTABキーを2回押すとクラスやメソッドのドキュメントを表示してくれます。
表示を戻すときはq
を押します。
従来のirbが好みだという人は、
--legacy
オプションを付けてirbを起動すると、シンタックスハイライトや複数行の入力履歴等が無効化されます。$ irb --legacy irb(main):001:0>自動インデントを無効化する場合は、
~/.irbrc
に以下の設定を追加します。# ~/.irbrc IRB.conf[:AUTO_INDENT] = false入力履歴はデフォルトで1000件保存されます。(
~/.irb_history
という履歴ファイルが作成されます)
履歴をもっと増やしたい、または減らしたい、という場合は~/.irbrc
で好みの件数を設定できます。(nilを設定すると入力履歴が無効化されます)# ~/.irbrc IRB.conf[:SAVE_HISTORY] = 2000参考: What's new in Interactive Ruby Shell (IRB) with Ruby 2.7 – Saeloun Blog
続いて、クラス単位でRuby 2.7の新機能や変更点を紹介していきます。
Array
&
と同じ振る舞いをするintersectionメソッドが追加されたRuby 2.7では両方の配列に含まれる要素を重複なく返す
intersection
メソッドが追加されました。
これは従来の&
演算子と同じ挙動になります。a = [1, 1, 3, 5] b = [3, 2, 1] # 2つの配列に共通して含まれる要素は1と3(重複なしで返す) a.intersection(b) #=> [1, 3] # Ruby 2.6以前では&演算子を使う方法しかなかった a & b #=> [1, 3]ちなみにRuby 2.6では
|
演算子と同じunion
メソッドと、-
演算子と同じdifference
メソッドが追加されました。a = [1, 2, 3] b = [3, 4, 5] # a | bと同じ a.union(b) #=> [1, 2, 3, 4, 5]a = [1, 2, 3, 4, 5] b = [0, 1, 2] # a - bと同じ a.difference(b) #=> [3, 4, 5]
|
と-
に名前付きのメソッドが追加されたのであれば、&
にもあった方がいいのでは?ということでintersection
メソッドが追加されたみたいです。Feature #16155: Add an Array#intersection method - Ruby master - Ruby Issue Tracking System
Array / Range
minmaxメソッドが高速化した
これまでArrayクラスの
minmax
メソッドはEnumerableモジュールによって提供されていましたが、Ruby 2.7では高速化のためArrayクラス自身に実装されました。# Ruby 2.6 [].method(:minmax) #=> #<Method: Array(Enumerable)#minmax> # Ruby 2.7 [].method(:minmax) #=> #<Method: Array#minmax()>Rangeクラスについても同様に
minmax
メソッドがRangeクラス自身に実装されました。# Ruby 2.6 (1..3).method(:minmax) #=> #<Method: Range(Enumerable)#minmax> # Ruby 2.7 (1..3).method(:minmax) #=> #<Method: Range#minmax()>Comparable
clampメソッドに範囲オブジェクトを渡せるようになった
Ruby 2.7ではclampメソッドに範囲オブジェクトが渡せるようになりました。
# Ruby 2.6以前 -1.clamp(0, 2) #=> 0 1.clamp(0, 2) #=> 1 3.clamp(0, 2) #=> 2 # Ruby 2.7からは範囲オブジェクトも渡せる -1.clamp(0..2) #=> 0 1.clamp(0..2) #=> 1 3.clamp(0..2) #=> 2ちなみに、clampメソッドはRuby 2.4で追加された比較的新しいメソッドです。
参考 指定された範囲内の値を返すようにするComparable#clamp
Complex(複素数オブジェクト)
<=>
メソッドが追加され、条件付きで大小比較ができるようになったRuby 2.7では複素数オブジェクトに
<=>
メソッドが追加され、以下のような比較をしても例外が発生しなくなりました。# Ruby 2.6 0 <=> 0i #=> NoMethodError (undefined method `<=>' for (0+0i):Complex) # Ruby 2.7 0 <=> 0i #=> 0ただし、以下の条件を満たさない場合はnilが返ります。
- 複素数オブジェクトの虚部がゼロである
- 比較対象のオブジェクトが実数、または虚部がゼロの複素数オブジェクトである
# 虚部がゼロでなければnil Complex(2, 3) <=> Complex(2, 3) #=> nil Complex(2, 3) <=> 1 #=> nil # 比較対象のオブジェクトが実数でないのでnil Complex(2) <=> :a #=> nil # 虚部がゼロ、かつ比較対象のオブジェクトが実数なので比較可能 Complex(5) <=> 2 #=> 1 # 虚部がゼロ、かつ比較対象のオブジェクトが虚部ゼロの複素数なので比較可能 Complex(5) <=> Complex(2) #=> 1Dir
globと
[]
にヌル文字で区切るパターンを渡すと例外が発生するようになったRuby 2.7では
Dir.glob
とDir.[]
にヌル文字で区切るパターンを渡すと例外が発生するようになりました。Dir.glob("foo\0bar") #=> ArgumentError (nul-separated glob pattern is deprecated) Dir["foo\0bar"] #=> ArgumentError (nul-separated glob pattern is deprecated)ちなみにRuby 2.6がリリースされたときには警告が出るようになっていて、段階的にこの機能が削除されたことになります。
# Ruby 2.6 Dir.glob("foo\0bar") #=> warning: use glob patterns list instead of nul-separated patternsEncoding
CESU-8エンコーディングが追加された
Ruby 2.7ではCESU-8エンコーディング(Wikipedia)が使えるようになりました。
s = "いろは" encoded = s.encode("CESU-8") encoded.encoding == Encoding::CESU_8 #=> trueEnumerable
select(filter)とmapを同時に行うfilter_mapメソッドが追加された
Ruby 2.7ではselect(filter)とmapを同時に行うfilter_mapメソッドが追加されました。
このメソッドを使うとブロックの戻り値が真であるものだけがmapの結果として返されます。numbers = [1, 2, 3, 4, 5] # filter_mapを利用して偶数の要素だけ値を10倍する(奇数は要素から除外) numbers.filter_map { |n| n * 10 if n.even? } #=> [20, 40] # Ruby 2.6以前だと下のどちらかの書き方になる numbers.select(&:even?).map { |n| n * 10 } numbers.map { |n| n * 10 if n.even? }.compactなお、破壊的な
filter_map!
メソッドは用意されていないようです。numbers.filter_map! { |n| n * 10 if n.even? } #=> NoMethodError (undefined method `filter_map!' for [1, 2, 3, 4, 5]:Array)要素ごとの個数をカウントするtallyメソッドが追加された
Ruby 2.7では要素ごとの個数をカウントするtallyメソッドが追加されました。
order = ['ピザ', 'パスタ', 'ピザ', 'ドリア', 'ドリア'] order.tally #=> {'ピザ' => 2, 'パスタ' => 1, 'ドリア' => 2} # Ruby 2.6以前で同等のことを実現するコード例 order.each_with_object(Hash.new(0)) { |o, h| h[o] += 1 } #=> {'ピザ' => 2, 'パスタ' => 1, 'ドリア' => 2}ちなみに、tallyという名前は線の本数で数を表現する、tally marks(画線法)に由来するそうで
Enumerator
自由なデータ変更を伴いつつ、無限のシーケンスを生成できるproduceメソッドが追加された
Ruby 2.7では自由なデータ変更を伴いつつ無限のシーケンスを生成できるproduceメソッドが追加されました。
# 1を順番にインクリメントさせるシーケンスから最初の5つを取り出す # (第1引数の1はシーケンスの初期値) Enumerator.produce(1, &:succ).take(5) #=> [1, 2, 3, 4, 5] # 0から999までのランダムな数字を5つ作成する(初期値を与えないパターン) Enumerator.produce { rand(1000) }.take(5) #=> [110, 725, 554, 755, 861]こちらの記事にはproduceメソッドを使ってフィボナッチ数列を作成するコード例が載っていました。
# https://blog.saeloun.com/2019/11/27/ruby-2-7-enumerator-produce Enumerator.produce([0, 1]) { |base_1, base_2| [base_2, base_1 + base_2] }.take(10).map(&:first) #=> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]lazyなenumeratorを非lazyなenumeratorに変換するeagerメソッドが追加された
Ruby 2.7ではlazyなenumeratorを非lazyなenumeratorに変換するeagerメソッドが追加されました。
# lazyなenumeratorを作成する lazy = [1, 2, 3].lazy.map { |x| x * 2 } # 非lazyなenumeratorに変換する enum = lazy.eager # Enumeratorのインスタンスになっている # (eagerを呼ばなければEnumerator::Lazyのインスタンス) enum.class #=> Enumerator # 非lazyなので普通に配列で返ってくる # (lazyのままだと、またEnumerator::Lazyが返るので、to_aする必要がある) enum.map { |x| x / 2 } #=> [1, 2, 3]Feature #15901: Enumerator::Lazy#eager - Ruby master - Ruby Issue Tracking System
Enumerator::Yielderオブジェクトにto_procメソッドが実装された
Enumerator::Yielderオブジェクトが
to_proc
メソッドを実装したため、<<
や.yield
を使わなくても(&y)
のような記法で済むようになりました。# Ruby 2.6以前 # (ブロック引数のyがEnumerator::Yielderオブジェクト) enum = Enumerator.new { |y| # y << i の代わりに y.yield i と書いても良い (1..3).each { |i| y << i } } enum.map { |i| i * 10 } #=> [10, 20, 30] # Ruby 2.7 enum = Enumerator.new { |y| # (&y)でOK (1..3).each(&y) } enum.map { |i| i * 10 } #=> [10, 20, 30]Feature #15618: Implement Enumerator::Yielder#to_proc - Ruby master - Ruby Issue Tracking System
Fiber
resumeすると同時にresumeされたfiber内で例外を発生させるraiseメソッドが追加された
Ruby 2.7はresumeすると同時にresumeされたfiber内で例外を発生させるraiseメソッドが追加されました。
fib = Fiber.new do counter = 0 loop { counter += Fiber.yield } counter end fib.resume fib.resume(10) fib.resume(100) # raiseメソッドを使ってfiber内でStopIteration例外を発生させ、ループを終了させる # ループが終了するとcounterの値が返ってくる fib.raise(StopIteration) #=> 110Feature #10344: [PATCH] Implement Fiber#raise - Ruby master - Ruby Issue Tracking System
File
ドットで終わる文字列をextnameメソッドに渡すとドットを返すようになった
Ruby 2.7ではドットで終わる文字列をextnameメソッドに渡すとドットを返すようになりました。
# Ruby 2.6 File.extname('foo.') #=> "" # Ruby 2.7 File.extname('foo.') #=> "."FrozenError
例外の発生原因となった凍結されたオブジェクトをreceiverメソッドで受け取れるようになった
Ruby 2.7では例外の発生原因となった凍結されたオブジェクトをreceiverメソッドで受け取れるようになりました。
frozen_str = 'abc'.freeze begin frozen_str.upcase! rescue FrozenError => err # receiverメソッドで、例外の発生原因となったオブジェクトを取得できる err.receiver #=> "abc" # 元のオブジェクトと同一のオブジェクトが返るので、equal?で比較するとtrueが返る err.receiver.equal?(frozen_str) #=> true endFrozenErrorオブジェクトをnewする際は、
receiver:
オプションでreceiver
メソッドが返すオブジェクトを指定できます。frozen_str = 'abc'.freeze e = FrozenError.new('test', receiver: frozen_str) e.receiver.equal?(frozen_str) #=> trueGC
断片化したメモリをデフラグするGC.compactメソッドが導入された
Ruby 2.7では断片化したメモリをデフラグするGC.compactメソッドが導入されました。
# 断片化したメモリをデフラグする GC.compact技術的な解説はRuby 2.7.0.preview3のリリースノートより引用します。
一部のマルチスレッドなRubyプログラムを長期間動かし、マーク&スイープ型GCを何度も実行していると、メモリが断片化してメモリ使用量の増大や性能の劣化を招くことが知られています。
Ruby 2.7ではGC.compact というメソッドを導入し、ヒープをコンパクションすることが出来るようになります。ヒープ内の生存しているオブジェクトを他のページに移動し、不要なページを解放できるようになるとともに、ヒープをCoW (Copy on Write) フレンドリーにすることが出来ます。 [Feature #15626]
IO
BOMを見て外部エンコーディングを判別できるset_encoding_by_bomメソッドが導入された
Ruby 2.7ではBOMを見て外部エンコーディングを判別できるset_encoding_by_bomメソッドが導入されました。
ただし、ファイルをバイナリモードで開いていなかったり、すでに外部エンコーディングが設定されていたりした場合はArgumentErrorが発生します。# BOMが付いているファイルをバイナリモード("rb"の"b")で開く io = File.open("test/fixtures/bom.txt", "rb") # デフォルトの外部エンコーディングはASCII_8BIT io.external_encoding #=> Encoding::ASCII_8BIT # encodingを判別する io.set_encoding_by_bom #=> Encoding::UTF_8 # 外部エンコーディングがUTF-8に変わる io.external_encoding #=> Encoding::UTF_8以下はBOMが付いていないUTF-8のテキストファイルを開いた場合の挙動です。
# BOMが付いていないファイルをバイナリモードで開く io = File.open("test/fixtures/no_bom.txt", "rb") # デフォルトの外部エンコーディングはASCII_8BIT io.external_encoding #=> Encoding::ASCII_8BIT # encodingを判別する(BOMがないのでnilが返る) io.set_encoding_by_bom #=> nil # 外部エンコーディングはASCII_8BITのまま io.external_encoding #=> Encoding::ASCII_8BITInteger
[]
メソッドがビットの範囲を指定できるようになったRuby 2.7では
[]
メソッドを使って指定した範囲のビットの値を取り出せるようになりました。# 0番目のビットから4桁ぶん取得する 0b01001101[0, 4] #=> 0b1101 (10進数では13) # 0番目から3番目までのビットを取得する([0, 4]と同じ) 0b01001101[0..3] #=> 0b1101 # 整数値を指定すると、その位置のビットの値を返す(Ruby 2.6以前からの仕様) 0b01001101[0] #=> 1 0b01001101[1] #=> 0 0b01001101[2] #=> 1 0b01001101[3] #=> 1Kernel
PathnameメソッドにPathnameオブジェクトを引数として渡すと引数そのものを返すようになった
Ruby 2.7では
Kernel#Pathname
メソッドにPathnameオブジェクトを引数として渡すと引数そのものを返すようになりました。pathname = Pathname.new('/tmp') # Kernel#PathnameにPathnameオブジェクトを渡すと引数自身が返る Pathname(pathname).equal?(pathname) #=> trueなお、上のコードでは大文字で始まる
Pathname
が2回出てきますが、前者がクラス名(定数)で、後者はメソッドである点に注意してください。# Pathnameはクラス名(定数) Pathname.new('/tmp') # Pathnameは(大文字で始まっているが)メソッド Pathname(pathname)Method
inspectメソッドがメソッドの引数や定義場所の情報も返すようになった
Ruby 2.7では
Method#inspect
メソッドが、メソッドの引数や定義場所の情報も返すようになりました。class SampleClass def example_for_inspect(a, b=nil, *c, d:, e: nil, **rest, &block) end end # Ruby 2.6 SampleClass.new.method(:example_for_inspect).inspect #=> "#<Method: SampleClass#example_for_inspect>" # Ruby 2.7 SampleClass.new.method(:example_for_inspect).inspect #=> "#<Method: SampleClass#example_for_inspect(a, b=..., *c, d:, e: ..., **rest, &block) (your path)/sample_class.rb:2>"Module
定数の定義場所を返すconst_source_locationメソッドが追加された
Ruby 2.7では
Module#const_source_location
メソッドを使って、定数の定義場所を確認できるようになりました。module ConstantExample FOO = 123 end # const_source_locationメソッドを呼ぶと、定義場所のパスと行番号が返る ConstantExample.const_source_location(:FOO) #=> ["(your path)/const_example.rb", 2]autoload?メソッドがinheritフラグを受け取れるようになった(未執筆)
(僕は使い方がいまいちよくわからなかったので、@tmtmsさんの以下のブログ記事を参照してください?)
nameメソッドが常に凍結された文字列を返すようになった(試験的な変更)
Ruby 2.7では
Module#name
メソッドが常に凍結された文字列を返すようになりました。
ただし、これは試験的な変更です。name = Time.name p name #=> "Time" # Ruby 2.6 name.frozen? #=> false # Ruby 2.7 name.frozen? #=> trueModule / Proc
ruby2_keywordsメソッドが追加された(未執筆)
(僕は使い方がいまいちよくわからなかったので、@tmtmsさんの以下のブログ記事を参照してください?)
NilClass / TrueClass / FalseClass
to_sメソッドを呼ぶと凍結された文字列を返すようになった(試験的な変更)
Ruby 2.7では
nil
/true
/false
に対してto_s
メソッドを呼ぶと、凍結された文字列を返すようになりました。
ただし、これは試験的な変更です。s = nil.to_s p s #=> "" # Ruby 2.7(Ruby 2.6ではfalse。以下同様) s.frozen? #=> true s = true.to_s p s #=> "true" s.frozen? #=> true s = false.to_s p s #=> "false" s.frozen? #=> trueObjectSpace::WeakMap
true/falseやシンボルなど、特別なオブジェクトをキーや値に指定できるようになった
Ruby 2.7ではtrue/falseやシンボルなど、特別なオブジェクトをWeakMapのキーや値に指定できるようになりました。
wm = ObjectSpace::WeakMap.new x = 'Hello' wm[true] = x wm[true] #=> Hello wm[nil] = x wm[nil] #=> Hello wm[1] = x wm[1] #=> Hello wm[:foo] = x wm[:foo] #=> Hello # Ruby 2.6ではエラーになる wm[true] = x #=> ArgumentError (cannot define finalizer for TrueClass)Proc
to_sメソッドの戻り値に
@
が含まれなくなった# Ruby 2.6 ->{}.to_s #=> "#<Proc:0x00007fba7c952db8@(irb):36 (lambda)>" # Ruby 2.7 ->{}.to_s #=> "#<Proc:0x00007fda4c934c38 (irb):41 (lambda)>"なお、この変更は
@
ではなくスペースで区切られていれば、ターミナルでobject_idをダブルクリックしたときにobject_idだけをきれいに選択できるから、という理由で導入されたようです。RubyVM
RubyVM.resolve_feature_path
が$LOAD_PATH.resolve_feature_path
に移動した(未執筆)(僕は詳しい内容がよくわからなかったので、@tmtmsさんの以下のブログ記事を参照してください?)
String
Unicodeバージョンが12.1.0に、Unicide Emojiバージョンが12.1になった
Ruby 2.7ではUnicodeバージョンが12.1.0に、Unicide Emojiバージョンが12.1になりました。
RbConfig::CONFIG['UNICODE_VERSION'] #=> 12.1.0 RbConfig::CONFIG['UNICODE_EMOJI_VERSION'] #=> 12.1ちなみにUnicode 12.1では「令和」の合字が追加されています。
12.1 では日本の新年号「令和」の合字一文字のみを加えており、総文字数は 137,929 となりました。
The Unicode Blog: Unicode コンソーシアムは「令和」をサポートする Unicode 12.1 を正式リリースしました
Symbol
start_with?メソッドとend_with?メソッドが追加された
Ruby 2.7ではSymbolクラスに
start_with?
メソッドとend_with?
メソッドが追加されました。
文字通りレシーバであるシンボルが指定された文字列で始まっていれば(または終わっていれば)trueを、そうでなければfalseを返します。:foo_bar.start_with?('foo') #=> true :foo_bar.start_with?('oo') #=> false :foo_bar.end_with?('bar') #=> true :foo_bar.end_with?('ba') #=> falseちなみに引数は文字列で指定します。シンボルで指定すると例外が発生します。
:foo_bar.start_with?(:foo) #=> TypeError (no implicit conversion of Symbol into String)Time
ミリ秒以下を切り上げ/切り下げできるceil/floorメソッドが追加された
Ruby 2.7ではTimeクラスにミリ秒以下を切り上げ/切り下げできるceil/floorメソッドが追加されました。
# ミリ秒以下に10桁の値を持つTimeオブジェクトを作成する t = Time.utc(2010, 3, 30, 5, 43, "25.0123456789".to_r) t.iso8601(10) #=> 2010-03-30T05:43:25.0123456789Z # ミリ秒以下を切り上げる t.ceil(0).iso8601(10) #=> 2010-03-30T05:43:26.0000000000Z t.ceil(5).iso8601(10) #=> 2010-03-30T05:43:25.0123500000Z # ミリ秒以下を切り下げる t.floor(0).iso8601(10) #=> 2010-03-30T05:43:25.0000000000Z t.floor(5).iso8601(10) #=> 2010-03-30T05:43:25.0123400000Zinspectメソッドがミリ秒の情報も返すようになった
Ruby 2.7では
Time#inspect
メソッドがミリ秒の情報も返すようになりました。t = Time.new(2010, 3, 30, 5, 43, "25.0123456789".to_r, '+09:00') # Ruby 2.6ではinspectもto_sも返ってくる文字列は同じ t.to_s #=> 2010-03-30 05:43:25 +0900 t.inspect #=> 2010-03-30 05:43:25 +0900 # Ruby 2.7ではinspectメソッドがミリ秒以下の情報も返す t.inspect #=> 2010-03-30 05:43:25 123456789/10000000000 +0900UnboundMethod
bindとcallを同時に行うbind_callメソッドが追加された
Ruby 2.7ではbindとcallを同時に行う
UnboundMethod#bind_call
メソッドが導入されました。# https://github.com/ruby/ruby/blob/v2_7_0_rc2/NEWS から引用 class Foo def add_1(x) x + 1 end end class Bar < Foo def add_1(x) # override x + 2 end end obj = Bar.new p obj.add_1(1) #=> 3 p Foo.instance_method(:add_1).bind(obj).call(1) #=> 2 p Foo.instance_method(:add_1).bind_call(obj, 1) #=> 2標準ライブラリ関連のアップデート
Date
令和の和暦を"R01"のように表示したりパースしたりできるようになった
Ruby 2.7では令和の和暦を"R01"のように表示したりパースしたりできるようになりました。
require 'date' reiwa_date = Date.new(2019, 5, 1) reiwa_date.jisx0301 #=> R01.05.01 Date.parse("R01.05.01") #=> #<Date: 2019-05-01 ((2458605j,0s,0n),+0s,2299161j)>ERB
ERBインスタンスをマーシャリングしようとすると例外が発生するようになった
Ruby 2.7ではERBインスタンスをマーシャリングしようとすると例外が発生するようになりました。
require 'erb' str = "hoge" erb = ERB.new("value = <%= str %>") # ERBインスタンスをマーシャリングすると例外が発生する Marshal.dump(erb) #=> TypeError (singleton class can't be dumped)open-uri
Kernelのopenメソッドを使うと警告が出るようになった
Ruby 2.7では
open-uri
ライブラリが提供しているopen
メソッドを使うと警告が出るようになります。require 'open-uri' # open-uriが提供しているopenメソッドを使うと警告が出る open("http://www.ruby-lang.org/") #=> warning: calling URI.open via Kernel#open is deprecated, call URI.open directly # 代わりにURI.openメソッドを使う # (この場合も事前に'open-uri'ライブラリをrequireしておく) URI.open("http://www.ruby-lang.org/")なお、この変更はRuby 2.5のリリース時に予告されていました。
URI.open method defined as an alias to open-uri's Kernel.open. open-uri's Kernel.open will be deprecated in future.
Webページを開いた際のデフォルトの文字コードがUTF-8になった
Ruby 2.7ではWebページを開いた際のデフォルトの文字コードがUTF-8になりました。
require 'open-uri' URI.open("http://www.ruby-lang.org/") do |f| puts f.charset #=> utf-8 endOptionParser
不明なオプションが渡されると"Did you mean?"が表示されるようになった
Ruby 2.7のOptionParserでは不明なオプションが渡されると"Did you mean?"が表示されるようになりました。
option_parser_sample.rbrequire 'optparse' OptionParser.new do |opts| opts.on("-f", "--foo", "foo") {|v| } opts.on("-b", "--bar", "bar") {|v| } opts.on("-c", "--baz", "baz") {|v| } end.parse!$ option_parser_sample.rb --fooo Traceback (most recent call last): option_parser_sample.rb:7:in `<main>': invalid option: --fooo (OptionParser::InvalidOption) Did you mean? fooPathname
globメソッドにオプション引数としてbaseが渡せるようになった
Ruby 2.7では
Pathname.glob
メソッドのオプション引数としてbase:
が渡せるようになりました。
この引数はDir.glob
メソッド(Dir.[]
メソッド)のbase:
オプションに委譲されます。[PARAM] base:
カレントディレクトリの代わりに相対パスの基準にするベースディレクトリを指定します。指定した場合、結果の頭にはベースディレクトリはつかないので、絶対パスが必要な場合はベースディレクトリを追加する必要があるでしょう。require 'pathname' path = '/tmp' # /tmpディレクトリ以下のパスを表すPathnameオブジェクトの配列が返る Pathname.glob("*", File::FNM_DOTMATCH, base: path) #=> [#<Pathname:.>, #<Pathname:..>, ...]その他
Ruby 2.7ではこれ以外にもさまざまな変更点がありますが、全部は説明しきれないのでNEWSのページに載っていた内容で、ここまでに説明していない情報を箇条書きにしておきます。
- RubyGemsのバージョンが3.1.1になった(参考)
- Bundlerのバージョンが2.1.0になった(参考)
- エスケープ文字が含まれる場合のCGI.escapeHTMLメソッドの呼び出しが2倍から5倍速くなった(参考)
- CSVのバージョンが3.1.2になった(参考)
- Object#DelegateClass accepts a block and module_evals it in the context of the returned class, similar to Class.new and Struct.new.
- Add Net::FTP#features to check available features, and Net::FTP#option to enable/disable each of them. [Feature #15964]
- Add ipaddr optional parameter to Net::HTTP#start to replace the address for TCP/IP connection [Feature #5180]
- Net::IMAP / Add Server Name Indication (SNI) support. [Feature #15594]
- Racc / Merge 1.4.15 from upstream repository and added cli of racc.
- Reline / New stdlib that is compatible with readline stdlib by pure Ruby and also has a multiline mode.
- REXMLのバージョンが3.2.3になった(参考)
- RSSのバージョンが0.2.8になった(参考)
- StringScannerのバージョンが1.0.3になった(参考)
- 以下のライブラリがbundled gemでなくなり、明示的なgemのインストールが必要になった
- CMath (cmath gem)
- Scanf (scanf gem)
- Shell (shell gem)
- Synchronizer (sync gem)
- ThreadsWait (thwait gem)
- E2MM (e2mmap gem)
- 以下のライブラリがdefault gemとしてrubygems.orgに公開された
- benchmark
- cgi
- delegate
- getoptlong
- net-pop
- net-smtp
- open3
- pstore
- singleton
- 以下のライブラリもdefault gemになった。ただし、rubygems.orgにはまだ公開されていない
- monitor
- observer
- timeout
- tracer
- uri
- yaml
Profiler__
モジュールとprofile.rbが標準ライブラリから削除された(Ruby 2.0から誰もメンテナンスしていないため)- パフォーマンス、もしくは実装上の改善
- Fiber
- File.realpath
- Hash
- MonitorとMonitorMixinのパフォーマンスが改善された
- Thread
- JIT
- その他の変更
- RubyのビルドにC99に対応したコンパイラが必要になった
- IA64アーキテクチャのサポートが削除された
- 開発リポジトリがSubversionからGitに変更された(ただし、GitHubではなく https://git.ruby-lang.org/ruby.git でホストされている)
- RUBY_REVISIONが数値ではなく文字列になった(リポジトリがSubversionからGitに変わった影響)
- RUBY_DESCRIPTIONにSubversionのrevisionではなく、Gitのrevisionが含まれるようになった
- Support built-in methods in Ruby with
_builtin
syntax.まとめ
というわけで、この記事ではRuby 2.7で導入されたさまざまな新機能や変更点を説明しました。
いやあ、今年は例年以上に変更点が多いですね!!
「Rubyは死んだ」とか言ってるのはいったい誰なんでしょうか?個人的には
Enumerable#tally
やEnumerable#filter_map
の使用頻度が高くなりそうな予感がします。
Rubyって配列やハッシュ周りのメソッドがめちゃくちゃ豊富なのが好きです、僕は。irbの進化もすさまじくて、簡単な動作確認がすこぶる効率アップしそうですね。
そして、今年も楽しいクリスマスプレゼントを届けてくれたMatzさんやコミッタのみなさんに感謝したいと思います。どうもありがとうございました!
みなさんもぜひRuby 2.7の新機能を試してみてください?あわせて読みたい
本記事の中でもいくつか紹介しましたが、@tmtmsさんも毎年アドベントカレンダーでRubyの新機能を解説されています。
僕が説明を端折った機能も丁寧に解説されているので、こちらも一緒にチェックすることをお勧めします。Ruby 2.7 Advent Calendar 2019 - Qiita
Ruby 2.3〜2.6の新機能は以下の記事にまとめてあります。
こちらもあわせてどうぞ。
- サンプルコードでわかる!Ruby 2.3の主な新機能 - Qiita
- サンプルコードでわかる!Ruby 2.4の新機能と変更点 - Qiita
- サンプルコードでわかる!Ruby 2.5の主な新機能と変更点 Part 1 - Qiita
- サンプルコードでわかる!Ruby 2.5の主な新機能と変更点 Part 2 - Qiita
- サンプルコードでわかる!Ruby 2.6の主な新機能と変更点 - Qiita
PR: 本記事を読んでもよくわからなかったRuby初心者の方へ
「本文に一通り目を通してみたけど、なんかよくわからない用語がたくさん出てきて、イマイチちゃんと理解できなかった?」というRuby初心者の方は、拙著「プロを目指す人のためのRuby入門」(通称チェリー本)を読んでみてください。
本書の内容を一通り理解すれば、この記事の内容も問題なく読みこなせるはずです!ちなみに本書の対象バージョンはRuby 2.4.1ですが、Ruby 2.5以降で発生する記述内容との差異は、それぞれ以下の記事にまとめてあります。なので、多少バージョンが古くても安心して読んでいただけます?
- Ruby 2.5で発生する「プロを目指す人のためのRuby入門」との差異について - Qiita
- Ruby 2.6で発生する「プロを目指す人のためのRuby入門」との差異について - Qiita
- (Ruby 2.7で発生する差異も執筆する予定)
- 投稿日:2019-12-24T09:39:36+09:00
attr_accessorについて
はじめに
attr_accessorについて厳密に理解していないとこがあったので整理してみます
attr_accessorとはなんぞや
sample.rbclass Book def title @title end def title=(val) @title = val endしっくり来ない
attr_accessorはattr_readerとattr_writerを合わせたものということは分かってる人は多いと思います。
なので、とりあえずattr_readerとattr_writerがそれぞれどのような役割があるのか見てみたいと思います。超初学者の気持ちになって順をおって考えてみる
atter_reader
まずはBookクラスを用意
qiita.rbclass Cafe endインスタンスを生成
qiita.rbclass Cafe def initialize(name) @name = name end end cafe = Cafe.new("Star Backs")「新たに作ったインスタンス変数@nameの名前を出力したいな。でもインスタンス変数を外部から扱うことことは出来ないな。」
当然インスタンス変数は外部から参照出来ないので以下のようにしてもエラーが出る
(こんなことやるやつ居ないと思いますが。)qiita.rbclass Cafe def initialize(name) @name = name end end cafe = Cafe.new("Star Backs") puts cafe.@nameファイルを実行
#エラー syntax error, unexpected tIVAR, expecting '(' puts cafe.@name ^~~~~@nameを識別できません
と表示される
「なら、nameというインスタンスメソッドを新たに定義しよう。」
qiita.rbclass Cafe def initialize(name) @name = name end def name @name end #インスタンスメソッドnameを定義 end cafe = Cafe.new("Star Backs")nameメソッドを定義できたら、nameメソッドを使ってインスタンス変数を出力する
qiita.rbclass Cafe def initialize(name) @name = name end def name @name end end cafe = Cafe.new("Star Backs") puts cafe.name #nameメソッド実行すると、ちゃんと出力される
$ ruby qiita.rb Star Backsつまり、インスタンス変数に外部から直接的に参照出来ないならば、インスタンスメソッド経由で間接的にインスタンス変数を参照しようという訳だ
でも一々インスタンスメソッド定義するのは面倒臭い
そこで登場するのがattr_reader
先ほど定義したnameメソッドは以下のように置き換えることができます
qiita.rbclass Cafe attr_reader :name #atter_readerに置き換える def initialize(name) @name = name end end cafe = Cafe.new("Star Backs") puts cafe.nameこれは楽ですね
atter_reader
今度はカフェの名前をTully'sに変更したいとします。
つまり、インスタンス変数@nameをStar BacksからTully'sに変更します。qiita.rbclass Cafe attr_reader :name def initialize(name) @name = name end end cafe = Cafe.new("Star Backs") puts cafe.name #Tully'sをインスタンス変数に再代入 cafe.name = "Tully's"これを実行しても
undefined method `name' for #<Cafe:0x0000000001b6dae0 @name="Star Backs"> (NoMethodError)nameメソッドはカフェクラスに定義されていません。
と表示されます。
インスタンス変数の値の変更はクラス内でしか出来ないので当然ですね。ならば、先ほど同様にインスタンスメソッド経由で変数に参照できるようにします。
qiita.rbclass Cafe attr_reader :title def initialize(name) @name = name end def name=(val) @name=(val) end #name=メソッドを定義 #引数で値を渡せるようにする 今回の場合は"Tully's" end cafe = Cafe.new("Star Backs") puts cafe.name cafe.name = "Tully's"qiita.rbclass Cafe attr_reader :name def initialize(name) @name = name end def name=(val) @name=(val) end end cafe = Cafe.new("Star Backs") puts cafe.name cafe.name = "Tully's" puts cafe.name #代入し直したインスタンス変数nameを出力実行してみる
$ ruby qiita.rb Star Backs Tully's大丈夫そうです
でも、これもいちいち定義するの面倒臭いそこで登場するのがattr_writer
先ほど定義したname=メソッドは以下のように置き換えることができます
qiita.rbclass Cafe attr_reader :name attr_writer :name #attr_writerに置き換える def initialize(name) @name = name end end cafe = Cafe.new("Star Backs") puts cafe.name cafe.name = ("Tully's") puts cafe.nameさらにattr_readerとattr_writerをまとめて1行にまとめられます。
qiita.rbclass Cafe attr_accessor :name #attr_accessorに置き換える def initialize(name) @name = name end end cafe = Cafe.new("Star Backs") puts cafe.name cafe.name = ("Tully's") puts cafe.nameまとめ
インスタンス変数を外部から参照することは出来ません
従って、外部から間接的に参照するためにメソッドを用意する必要があります
そのメソッドを1行で簡略化したものがattr_readerとattr_writerですさらにそのattr_readerとrattr_writerを1行に簡略化したものがattr_accessorということですね
もし間違っていることがあればご享受頂けると幸いです。
- 投稿日:2019-12-24T09:39:36+09:00
attr_accessorを順を追って理解してみた
はじめに
去年attr_accessorについてまとめた物がqiitaの下書きに眠っていたので投稿しておきます
attr_accessorとはなんぞや
attr_accessorはattr_readerとattr_writerを組み合わせたやつです
クラス外からインスタンス変数に参照したり変更する時に使われますattr_reader
huga.rbclass Huga def hoge @hoge end endattr_writer
huga.rbclass Huga def hoge=(val) @hoge=(val) end endattr_accessor
huga.rbclass Huga def hoge @hoge end def hoge=(val) @hoge=(val) end endいきなりコードだけ見てもしっくりこないですねattr_readerとattr_accessorについて順に考えてみます
順を追って考えてみる
atter_reader
まずはCafeクラスを用意
qiita.rbclass Cafe endインスタンスを生成
qiita.rbclass Cafe def initialize(name) @name = name end end cafe = Cafe.new("Star Backs")「新たに作ったインスタンス変数@nameの名前を出力したいな。でもインスタンス変数を外部から扱うことことは出来ないな。」
当然インスタンス変数は外部から参照出来ないので以下のようにしてもエラーが出る
(こんなことやるやつ居ないと思いますが。)qiita.rbclass Cafe def initialize(name) @name = name end end cafe = Cafe.new("Star Backs") puts cafe.@nameファイルを実行
#エラー syntax error, unexpected tIVAR, expecting '(' puts cafe.@name ^~~~~@nameを識別できません
と表示される
「なら、nameというインスタンスメソッドを新たに定義しよう。」
qiita.rbclass Cafe def initialize(name) @name = name end def name @name end #インスタンスメソッドnameを定義 end cafe = Cafe.new("Star Backs")nameメソッドを定義できたら、nameメソッドを使ってインスタンス変数を出力する
qiita.rbclass Cafe def initialize(name) @name = name end def name @name end end cafe = Cafe.new("Star Backs") puts cafe.name #nameメソッド実行すると、ちゃんと出力される
$ ruby qiita.rb Star Backsつまり、インスタンス変数に外部から直接的に参照出来ないならば、インスタンスメソッド経由で間接的にインスタンス変数を参照しようという訳だ
でも一々インスタンスメソッド定義するのは面倒臭い
そこで登場するのがattr_reader
先ほど定義したnameメソッドは以下のように置き換えることができます
qiita.rbclass Cafe attr_reader :name #atter_readerに置き換える def initialize(name) @name = name end end cafe = Cafe.new("Star Backs") puts cafe.nameこれは楽ですね
atter_reader
今度はカフェの名前をTully'sに変更したいとします。
つまり、インスタンス変数@nameをStar BacksからTully'sに変更します。qiita.rbclass Cafe attr_reader :name def initialize(name) @name = name end end cafe = Cafe.new("Star Backs") puts cafe.name #Tully'sをインスタンス変数に再代入 cafe.name = "Tully's"これを実行しても
undefined method `name' for #<Cafe:0x0000000001b6dae0 @name="Star Backs"> (NoMethodError)nameメソッドはカフェクラスに定義されていません。
と表示されます。
インスタンス変数の値の変更はクラス内でしか出来ないので当然ですね。ならば、先ほど同様にインスタンスメソッド経由で変数に参照できるようにします。
qiita.rbclass Cafe attr_reader :title def initialize(name) @name = name end def name=(val) @name=(val) end #name=メソッドを定義 #引数で値を渡せるようにする 今回の場合は"Tully's" end cafe = Cafe.new("Star Backs") puts cafe.name cafe.name = "Tully's"qiita.rbclass Cafe attr_reader :name def initialize(name) @name = name end def name=(val) @name=(val) end end cafe = Cafe.new("Star Backs") puts cafe.name cafe.name = "Tully's" puts cafe.name #代入し直したインスタンス変数nameを出力実行してみる
$ ruby qiita.rb Star Backs Tully's大丈夫そうです
でも、これもいちいち定義するの面倒臭いそこで登場するのがattr_writer
先ほど定義したname=メソッドは以下のように置き換えることができます
qiita.rbclass Cafe attr_reader :name attr_writer :name #attr_writerに置き換える def initialize(name) @name = name end end cafe = Cafe.new("Star Backs") puts cafe.name cafe.name = ("Tully's") puts cafe.nameさらにattr_readerとattr_writerをまとめて1行にまとめられます。
qiita.rbclass Cafe attr_accessor :name #attr_accessorに置き換える def initialize(name) @name = name end end cafe = Cafe.new("Star Backs") puts cafe.name cafe.name = ("Tully's") puts cafe.nameまとめ
インスタンス変数を外部から参照することは出来ません
従って、外部から間接的に参照するためにメソッドを用意する必要があります
そのメソッドを1行で簡略化したものがattr_readerとattr_writerですさらにそのattr_readerとrattr_writerを1行に簡略化したものがattr_accessorということですね
もし間違っていることがあればご享受頂けると幸いです。
- 投稿日:2019-12-24T09:29:14+09:00
Railsにマークダウン記法を取り入れる方法
結論
1,2,3を導入すればいい
1. gem 'redcarpet'
2.
module MarkdownHelper def markdown(text) options = { filter_html: true, hard_wrap: true, space_after_headers: true, with_toc_data: true } extensions = { autolink: true, no_intra_emphasis: true, fenced_code_blocks: true, tables: true } renderer = Redcarpet::Render::HTML.new(options) markdown = Redcarpet::Markdown.new(renderer, extensions) markdown.render(text).html_safe end end3 .
<%= markdown(@article.body).html_safe %>参考
https://musicamusik.hatenablog.com/entry/2018/06/09/181439エラーが出た場合
gemlockを確認するとgem 'redcarpet'がバージョン指定されている可能性があります。自分の場合、gemlockのバージョン指定を削除してbundle updateすることで解決しました。
- 投稿日:2019-12-24T09:07:49+09:00
Rails6 each文にてeachがメソッドとして扱われて困った話
目的
- each文を使用してカラム内容を順に出力したい時にeachがメソッドとして扱われてしまって困ったときの解決法をまとめる
結論
- each文で繰り返し処理の対象の変数に格納されている値を配列状態で格納する。
エラーの内容とエラー時のコード
- 当該ページは
@posts.content
に格納されている内容をeach文で次々渡し、表示する処理がある。当該ページへアクセスしたところ下記のエラーが発生した。
undefined method `each' for #<Post:0x00007fac1ebd28e8>エラーに関係するルーティングファイルの内容を記載する。
get "posts/index/:id" => "posts#index"エラーに関係するpostsコントローラファイルの内容を記載する。
def index @posts = Post.find_by(id: params[:id]) endエラーに関係するindexビューファイルの内容を記載する。
<% @posts.each do |post| %> <%= post.content %> <%= link_to("詳細", "/posts/#{post.id}") %> <% end %>解決方法
修正を行なったコードのみ下記に記載する。
def index @posts = Post.where(id: params[:id]) endfind_byメソッドは一致した最初のものを一つだけ返す。
whereメソッドは一致した全てのものを返す。
- 投稿日:2019-12-24T02:05:53+09:00
form_tag,form_withと空のnewアクション
はじめに
今スクールでpictweetという、写真とツイートを一緒に投稿できる、簡単な練習用のwebサービスをつくっているのですが、ふと気になったことがあったので記事にしてみました。
form_tag,form_withとnewアクション
MVCモデル学習し、ツイートが投稿されたときの流れも理解していたつもりでした。
投稿されたデータがデータベースに保存されるためにはnewアクションでインスタンス変数を定義しなければいけないと思っていたのですが、、、
サンプルコードを見るとqiita.rbdef new endえ???newアクションの中身なにもないやん!(◎◎;)
でもよく似たwebサービスのサンプルコードではpostsコントローラに「@post = Post.new」としっかりインスタンス変数が定義されていました。
何が違うの?
と思って数時間かけて調べた結果,HTMLでformtagを使用していた場合は、newアクションでインスタンス変数は定義しなくていいみたいです。
逆にform_withを使用する場合は@モデル名で定義しなければいけないみたいです。もう少し調べるとform_forというのも出てきて、そもそもform_forとform_tagは投稿されたデータをデータベースに保存するかしないかで使い分けるんですね。
ただ新しく作られたform_withは両方の用途で使用し、またセキュリティ面でも有能のようです。結果、これからは投稿フォーム系にはform_withを使うのがベストというのが今回の学びです。
- 投稿日:2019-12-24T01:07:52+09:00
Rustにチャレンジ 2019/12/4 adventcalender
今日は、AdventCalenderの4日目になります
Rust?
最近、SNSで話題になっているなーと思いつつ、せっかくなので空き時間で
チャレンジしてみました( ゚Д゚)使いやすい?
学習して合計5日ぐらいなので「俺たちはわからない、感覚で書いている」という状態です。
環境について
RustのPlayGround ( https://play.rust-lang.org/ ) を使いました
今回作ったときの手順
1.過去に作っていたプログラミング言語で簡単なプログラムを用意する
お仕事のソースコードとかは持ってこれないので、ある程度書きなれている言語で
お題を考えながら用意してみる僕は、Ruby(普段PHPやPythonのLL系が多いので)で以下のようなコードを用意しました
# Your code here! # XR簡易ゲームのコンソールイメージで3択ゲームとして実装 # じゃんけんなので、0=グー、1=ちょき、2=パー srand(Time.now.to_i) player_value = rand(3) # issue 正式版は、標準入力から取得をする # ローカルだとタイムアウトするので、sleepはコメントアウトする puts "じゃんけんを開始するよ" # 入力の代わりに3秒待たせる #sleep(3) puts "それじゃあ、いくよ" #sleep(1) puts "じゃーんけん" # 5秒ぐらい待っている #sleep(5) puts "ぽんっ" # お互いに手を繰り出すモーションの待機時間 #select(2) # 判定をする def judge(select_value) # CPU側のじゃんけんをランダムで実行をする # issue 倍率によって、じゃんけんで使う手立ての確率を変更する srand(Time.now.to_i) cpu_value = rand(3) result_hash = Array.new(3, Array.new(3, "")) # issue 定数の初期化で一括記載 result_hash[0][0] = "あいこだよ" result_hash[0][1] = "勝ちだよ" result_hash[0][2] = "負けだよ" result_hash[1][0] = "負けだよ" result_hash[1][1] = "あいこだよ" result_hash[1][2] = "勝ちだよ" result_hash[2][0] = "勝ちだよ" result_hash[2][1] = "負けだよ" result_hash[2][2] = "あいこだよ" hands = Array.new(3, "") hands[0] = "ぐー" hands[1] = "ちょき" hands[2] = "ぱー" puts "プレイヤー:%s CPU : %s 結果は %s" % [ hands[select_value] , hands[cpu_value] , result_hash[select_value][cpu_value] ] puts select_value puts cpu_value end # じゃんけんで入力のフェイクとしてスリープをする puts judge(player_value)Rustに移植してみたバージョンです extern crate rand; use rand::Rng; use std::process::Command; use std::string; use std::vec; fn main() { // let mut rng = rand::thread_rng(); // let i: i32 = rng.gen(); game_opening(); // 2秒待機 // じゃんけん判定処理を呼び出す judge(0); } fn waitfor_value(_w: string::String) -> () { let mut child = Command::new("sleep").arg(_w).spawn().unwrap(); let _result = child.wait().unwrap(); } // ゲームオープニング処理 fn game_opening() { println!("じゃんけんを開始するよ"); // 3秒 let before_string = "3"; waitfor_value(before_string.to_string()); println!("それじゃあ、行くよ"); // 1秒 let start_string = "1"; waitfor_value(start_string.to_string()); println!("じゃーんけん"); // 5秒 let action_string = "5"; waitfor_value(action_string.to_string()); println!("ぽんっ"); } // じゃんけん判定処理 fn judge(_s: i32) -> () { // CPU側のじゃんけんをランダムで実行をする // じゃんけんで、0~2を指定 let num = rand::thread_rng().gen_range(0, 2); // CPU手数 let cpu_num = rand::thread_rng().gen_range(0, 2); // じゃんけん判定用配列を用意 let display_name = vec!["グー", "チョキ", "パー"]; let display; // グーの場合 // issue 配列周りをplay-ground rustで書けるようにして、きれいにする if num == 0 { if cpu_num == 0 { display = "あいこ"; } else if cpu_num == 1 { display = "かち"; } else { display = "まけ"; } } else if num == 1 { if cpu_num == 0 { display = "まけ"; } else if cpu_num == 1 { display = "かち"; } else { display = "あいこ"; } } else { if cpu_num == 0 { display = "かち"; } else if cpu_num == 1 { display = "まけ"; } else { display = "あいこ"; } } // じゃんけんの結果を表示する // puts "プレイヤー:%s CPU : %s 結果は %s" % [ hands[select_value] , hands[cpu_value] , result_hash[select_value][cpu_value] ] println!("プレイヤー: {} CPU : {} 結果は {} だよ", display_name[num].to_string(), display_name[num].to_string(), display.to_string()); // puts select_value // puts cpu_value }改めて自分自身での移植をやってみてどうだったの
1.お題があると、プログラム言語の習熟に集中しやすい
言語の仕様を把握していない段階なので、サンプルがない状態でうーんと考えるよりは”これを移植しよう”って考えると言語学習に集中できました。
2.移植元があると、参考にしやすい
これは、他のアプリでもいいのですが参考になるものがあると挙動が違うな等
の参考になりました
- ちょっと転職とか、お話がしやすそう 移植業務が多かった方向けですが、"双方の言語が読める"と、"移植元と移植先の動きを確認しながら差異がある場合は、相談したり提案してくれる"という指標でポートフォリオに入れてもいいかなと思いました。
*コード修正やリプレースが多かったので
実行結果
youtubeのURLだけちらっと公開
この記事のissue
フローチャート追加する予定です
*ほかの日が終わったら、以上、次回はyumemi_vueの女の子を用意して後半レポートを書くよ( ゚Д゚)
- 投稿日:2019-12-24T01:07:27+09:00
Rubyであたまの体操
あたまの体操してますか?
以下は、 プログラマ脳を鍛える数学パズルを写経したときのメモです。回答例はRubyで記述したものです。もっとRubyのメソッドを活用すればきれいに書けるZE☆などあったら、コメントください。
Q1〜Q3まであります。前提条件の後に問題が表示されます。回答例を見る前にコーディングしてみると、あたまの体操になります。(たぶん)
Q1.カードを裏返したい
【前提】
1〜100までの番号が書かれた100枚のカードが1から順番に並んでいます。最初、すべてのカードは裏返しの状態になっています。ある人が2番目のカードから、1枚おきにカードを裏返していきます(2,4,6...100のカードが表を向く)。次に3番めのカードから2枚お気にカードを裏返していく。(表向きのカードは裏返され、裏向きのカードは表を向く)。このようにn番目のカードからn-1枚おきにカードを裏返す操作を、どのカードの向きも変わらなくなるまで続けたとします。【問題】
カードの向きが変わらなくなったとき、裏向きになっているカードの番号をすべて求めてください。【回答例】
# 100枚のカードを初期化 N = 100 cards = Array.new(N, false) # 2〜Nまで裏返す (2..N).each do |i| j= i -1 while (j < cards.size ) do cards[j] = !cards[j] j += i end end # 最後に裏向きのカードを出力する N.times do |i| puts i + 1 if !cards[i] end【考え方】
左から順番に処理していけばOK。
表向きを「true」, 裏向きを「false」とすることで、反転を表現できる。Q2.棒を切り分けたい
【前提】
長さn[cm]の一本の棒を、1cm単位に切り分ける。一本の棒を一度に切ることができるのは、1人だけ。例えば、切り分けられた棒が3本あるとき、同時に3人で切ることができる。
最大m人の人間がいるとき、最短で何回切り分けられるかを求めてください。【問題】
n=100, m=8のときの回数を求めよ。# currentは現在の棒の数 def cutbar(m, n , current) # 切り終えたとき if current >= n then 0 # 切る人より、現在の棒の数が少ない時、切れる elsif current < m then # 切れたら現在の2倍になる 1 + cutbar(m, n, current * 2) else # 切る人の数だけ増加 1 + cutbar(m, n, current + m) end end puts cutbar(8, 100, 1)【考え方】
一本の棒を切れるのは1人だけ。棒を切れるときの条件を整理する。
(問題を逆に読み、1cmの棒をm人で繋いで、n[cm]の棒を作ることとも読み替えられる→別解を考えてみよう!)Q3.どうしても現金で払いたい
【前提】
バスに設置されているような、両替機を想定してください。この機械では、10円、50円、100円、500円の組み合わせで両替することができる。両替する際に、使われない硬貨があっても構いませんが、大量の小銭は持て余すので、最大で15枚となるように両替します。例えば、「50円玉、20枚」という両替はNG。【問題】
1,000円札を入れた時の、硬貨の組み合わせは何通りになるか?
- 回答例[強引ver.]
count = 0 (0..2).each do |y500| (0..10).each do |y100| (0..15).each do |y50| (0..15).each do |y10| if y500 + y100 + y50 + y10 <= 15 then if y500 * 500 + y100 * 100 + y50 * 50 + y10 * 10 == 1000 then count +=1 end end end end end end puts count
- 回答例[メソッド活用ver.]
coins = [10, 50, 100, 500] count = 0 (2..15).each do |i| coins.repeated_combination(i).each do |coin_set| count +=1 if coin_set.inject(:+) == 1000 end end puts countおわりに
いかがでしたか?
今回は、ちょっとあたまの体操がしたいなという時に解いてみたいpuzzleをご紹介しました。
ぜひこの機会にあたまをやわらかくしてみてくださいね。(こちらの記事の情報は記事更新時点のものです。事前に最新のあたまの体操情報をご確認ください)
- 投稿日:2019-12-24T00:59:08+09:00
Rails の scope 便利手帳
Ruby on Rails で、使いやすいScopeをまとめました。
Model の設計によって差異があるものの、視点は使えるのでは?と思います。Scopeとは
Model に scope を記載することで、Controllerなどから、複雑なWhere条件を書かずにデータを取りだせるようになります。
app/model/article.rbclass Article < ApplicationRecord scope :newer, ->{order("updated_at DESC") } endこれによって、Controllerなどで
app/controllers/articles_controller.rb@articles = Article.newerで、更新日が新しい順に取得することができます。
app/controllers/article.rb@articles = Article.newer.limit(5)とすると、更新日が新しい順に5件を取得できます。
公開されているか
scope :published, -> { where(published: true) }ある時間より前か
scope :created_before, ->(dt) { where("created_at < ?", dt) }呼び出し元では、引数に時間をいれる
@articles = Article.created_before(Time.now)終了状態であるか
終了日の有無で判定する場合
scope :finished, ->{where("finished_at IS NOT NULL") }その他追記予定
- 投稿日:2019-12-24T00:50:19+09:00
【チーム開発】【GitHub】プログラミング未経験の営業が、GitHubでチーム開発を経験してみたお話
~はじめに~
この記事なに?
社会人で営業の経験しかしてこなかった26歳が、プログラミング独学で勉強して早半年。
MENTAというサービスで現役Railsエンジニアの方に教えて頂き、ちょっとだけチーム開発の経験をさせて頂きました。
『アウトプットが大切』ということを周りから聞いてますので頑張って書いてます。
~未経験の方に向けてチーム開発の大まかな流れが分かっていただければと思います~
自分のレベル感
- 営業歴8年…お話は得意です。
特にテキト〜な話は大好物。- Railsチュートリアル2周ほど
- GitとGithub使って2ヶ月ほど
- 基本情報技術者試験ぎりぎり合格。。。
- チーム開発は、もちろん未経験です。
で、なにするの?
https://github.com/Matsushin/qiitan
こちらQiitaを模倣したサイト『Qiitan』
過去MENTA生徒の方々と、Railsエンジニアの先生がコツコツ作ってきた汗と涙の結晶!こちらのサイトへ『追加機能を実装』していきます!
~現場によってやり方は様々ですので今回はあくまで一例になります~0: 前提条件
gitのインストールとgithubへのアカウント登録は済んでいること。
1: GithubからCloneして、ローカルリポジトリを作成
1:上記画像参照頂き、緑のボタンを押下
2:URLをそのまま全てコピー(一番右にあるボタンでもコピー可能)
3:ターミナルでコピペして下記を実行 ↓$ git clone https://github.com/xxx/xxx.git2: 作業用のブランチを作成する
そのまま作業をするとmasterブランチに直接コードを書いてしまう為、自分の実装内容にあったブランチ名をつけて作成を行う。
$ git checkout -b [ブランチ名] checkout → ブランチを移動する時に使う。 -b → checkoutに-b (-branchの略)をつけることで、ブランチの作成+移動を行う。3:Githubで『実装進捗の見える化』
Githubのの進捗管理として[Project]を設定でき、その中で自分の割り当てられたタスクをドラックして指定の位置へ。
この様にすることで『誰が今なんの機能を実装しているか』を把握することが出来ます。4: 作業用のブランチで実装スタート
先程作成した作業用ブランチで機能実装開始!
●実装に一区切りがついたら → addしてcommitをしよう!
$ git add . → 作業したファイルを全てステージングに乗せる$ git commit -m [メッセージの入力] → 上記の[]内に、終了したタスクのコメントを簡単に入力●与えられた機能の実装が全て終了したら → pushをしてGithubに上げよう!
$ git push origin [ブランチ名] → Github上に自分のブランチをpushで上げる事ができる。※今回の『Qiitan』では[CircleCi]を使用してますので、GithubにPushしたらCircleCiのテストが作動します。
URLにアクセスして、テストが通ることを確認してみましょう↓↓↓
5: Githubにpushをしたら、プルリクエストを
6: プルリクエスト後、[Project]内のタスク移動を忘れずに!
[Project]を開き、再度タスクをドラックして[Review]へ
あとは、リードプログラマのコードレビューを待ちます。
7: コードレビューをもらったら一つずつ修正を行っていく
~コードを全て修正後、再度push⇔コードレビューを繰り返します~
↑
こんな感じです8: コードレビューが全て完了したら、masterにマージする
9: 最後に忘れずに、[Project]内のタスクを移動して終了
こうすることで自分が実装した機能が、本流へ追加されます!
お疲れさまでした!〜最後に:チーム開発体験を終えて〜
チーム開発の大枠の流れを把握することが出来ました。
実際に既存のコードをさわる際に、既存コードの流用や真似をして記載するようにしていたが、
周りのコードに影響が出ないか。また必要であれば既存のコードへの加筆をしてもいいか、判断に迷いました。現在は個人開発しかしておりませんので、ブランチ名もクラス名もテキトーに付けてしまっている部分もありますが、
チーム開発ではそれは出来ません。日頃からそちらも意識して開発して参りますm(_ _)m
- 投稿日:2019-12-24T00:00:39+09:00
明白かつ現在のSlack:Slackの各種イベントを監視するbotを気軽に作れるようにした
この記事は『Slack Advent Calendar 2019』24日目の記事です。
3行でおk
- Slack上でのチャンネル作成などのイベントを検知するbotが欲しい
- 手軽にRubyでbotが作れる mobb というライブラリを使いたい
- mobb上で簡単に作れるreppハンドラをつくったよ
モチベーション
※このセクションはポエムなので、興味がない方とQiitaの運営の方は読み飛ばしてください
Slackで何が起こっているか、知ってますか?
Slackはとても便利な
ゲームプラットフォームチャットツールです。
複数拠点をまたいだやり取りや、リモートワークなどを支える
重要なコミュニケーションツールとしての地位を確立しつつあります。一方、会社や学校、大規模コミュニティなど多くの人が所属するチームで使うようになると
全体を見渡す、どこで何が起きているのかを認識することは難しくなります。ですが、Slackは多くのAPIを提供しています。
特に Real Time Messaging API や Events API を利用することで、
Slack上で起きたイベント(メンバーの追加、チャンネルの追加など)を認識することができます。
※botがReal Time Messaging APIを受け取り、その内容を元に人間にわかりやすく発言している図
新しいメンバー・チャンネル・絵文字の追加や削除などが通知されますただし作るのは微妙に面倒くさい
SlackのAPIは程よく難解です[要出典]
Events APIを利用するためにはHTTPSのエンドポイントを用意してメッセージを受け取る必要があります。
ちょっと10分くらいいじってみよ~という気持ちで触るにはハードルが高いですよね……。最も手軽なのは Bot integration での Real Time Messaging API の利用ですが、
Slack初心者が挑むとAPIレスポンスの反撃を受けることがあります。sinatraライクなmobbを利用したい
Rubyでしっかりとしたbotを作るのであれば Ruboty という選択肢があります。
すでに様々なアダプタが提供されているため、多くのチャットツールでも利用できます。一方、僕は別にお仕事でbotを作るわけではなく、
サクっと書いてサクっと動かしたい気持ちが強いです。そこで Mobb を使います。
Mobb は Sinatra にインスパイアされて作られたbotフレームワークで、ほぼ Sinatra のような書き味でbotを作ることができます。require 'mobb' set :service, 'slack' on 'ping' do 'pong !' end5秒で書けそう!
MobbでSlack監視botをつくる
Reppハンドラ?
Mobb で作られたbotの通信処理は Repp の中に定義されています。
Repp はいわゆる Rack のbotフレームワーク版のようなものとのことです。
- 秒でBotを作れるMobb、その裏にあるReppって何者?
Reppは標準でSlackハンドラを提供しているのですが、
あくまでチャット用途のものであり、通常のメッセージしか対象ではありません。ところで、実は Mobb/Repp の作者の方が既にSlack監視botを作成して公開しています。
- slack-watcher
この中身を見てみると、このbot専用にイベントだけを通知するSlackハンドラを定義して、
それを元にメッセージを送信しているようです。これをそのままパクるのもありですが、どうせなら普通の会話もイベント監視もできる
便利でカワイイbotを作れたら嬉しいので、今回はイベントにも対応したSlackハンドラを作ってみました!repp-heartful_slack をつくりました
今回つくった、ReppのSlackハンドラです。
Repp標準のSlackハンドラとの違いは、通常メッセージだけでなくイベント通知も取得できるようになっています。このハンドラを利用すると、以下のような形で書くことができます。
require 'mobb' require 'repp/heartful_slack' set :service, 'heartful_slack' # 通常メッセージだけを絞り込むフィルタ set :on_message do |_| condition { @env.is_a?(::Repp::HeartfulSlack::MessageReceive) } end # イベントメッセージだけを絞り込むフィルタ set :on_event do |_| condition { @env.is_a?(::Repp::HeartfulSlack::EventReceive) } end # 通知先の設定 # イベントは発言元のチャンネルがないので、これでチャンネル名を設定します set :to_notify do |_| dest_condition do |res| res.last[:channel] = ENV['NOTIFY_CHANNEL'] # ex. Cxxxxxxx end end # 通常メッセージ on 'ping', on_message: true do 'pong' end # イベント通知(チャンネルの追加) # 参考: https://api.slack.com/events/channel_created on 'channel_created', on_event: true, to_notify: true do "created -> <##{@env.raw.channel.id}>" # 「created -> #hoge」のように投稿される endこれで普通のbotとしての振る舞いもできるし、
イベント通知もできるbotが簡単に書けるようになります!1ハートフルですね!なお、残念なことにまだ全イベントには対応できていません。
イベントの種類が膨大なのと、そのイベントをどうやって使うかが想像しきれなかったものは省いています。もし、このイベントにこういう形で対応したら便利!というのがあれば
PullRequestもらえると嬉しいです。現時点でできることは以下のサンプルコードに書いてあります。
最後に
こういったイベント監視botは便利で愛されますが、
一部の人からは思いっきり嫌われます![]()
大きい組織に導入する場合は、強い心を持って導入するか、
自分だけが見られるチャンネルにこっそり導入しましょう。用法、容量を守って楽しくハッピーSlacking!
参考文献
- 今そこにあるSlack
- 【要注意!英語?表現】「ハートフル」はうまく伝わらない和製英語?表現 | Weblio英会話コラム(英語での言い方・英語表現)
メッセージとイベントが混ざって飛んでくるせいで、フィルタを毎回書かないといけなくなってることから目を背けながら ↩