20201012のRubyに関する記事は30件です。

今回使ったlink_toのいろいろ、まとめてみた

はじめに

今回のカリキュラムで一番躓いたのが、link_toメソッドだった。もちろん基本的な使い方は理解しているつもりだが、どうもpathの引数がしっくりときていない。

今回の投稿は何かを説明する目的ではなく、自分がコードを読むための記録である。
以下、今回作成したアプリの全link_toを余計な記述はカットして引数の読み方とともに載せる。

全link_toメソッド

application.html.erb
<%= link_to "ログアウト", destroy_user_session_path, method: :delete %>
<%= link_to "新規投稿", new_prototype_path %>

サインイン状態で表示させている「ログアウト」と「新規投稿」どちらも誰もが共通するページや処理のため、( )で引数を渡す必要がない。
ユーザーによってログアウトページが違うことはないし、ユーザーによって新規投稿ページが違うことはない。
link_toメソッドはデフォルトでHTTPメソッドがGETのため、ログアウトのときは、第三引数にメソッドを指定する。

application.html.erb
<%= link_to "ログイン", new_user_session_path %>
<%= link_to "新規登録", new_user_registration_path %>

①と考え方は同じ。今度は非ログイン状態のときの表示。ログイン画面も新規登録もユーザーによって変わることはないから、引数がなくてよい。

application.html.erb
<%= link_to image_tag("logo.png"), root_path %>

root_pathは引数がなくてもいける!

prototypes/show.html.erb
<%= link_to "編集する", edit_prototype_path(@prototype) %>
<%= link_to "削除する", prototype_path(@prototype), method: :delete %>

投稿したものの詳細ページから、編集したり、削除したりする部分。
showアクションの中で、@prototype = Prototype.find(params[:id])と一つのレコードを選び出しているため、どのprototypeを編集・削除するか判断ができる。

prototypes/_prototype.html.erb
<%= link_to prototype.title, prototype_path(prototype.id) %>
<%= link_to image_tag(prototype.image), prototype_path(prototype.id) %>
user_pathシリーズ

user_pathはユーザーのマイページに飛ぶ。誰のマイページなのか明らかにするため、引数が必要になる。

prototypes/index.html.erb
<%= link_to current_user.name + "さん", user_path(current_user) %>

ログイン中のユーザー名をクリックするとそのユーザーのマイページに飛ぶ。
deviseのGemを用いることによって使えるcurrent_user

prototypes/_prototype.html.erb
<%= link_to "by " + prototype.user.name, user_path(prototype.user.id) %>

prototypeとアソシエーションしているuserのidを取得。

prototypes/show.html.erb
<%= link_to "by " + @prototype.user.name, user_path(@prototype.user.id) %>

誰による投稿か、名前が表示されている部分のコード。
showアクションの中で、@prototype = Prototype.find(params[:id])と一つのレコードを選び出しているため、prototypeとアソシエーションしているuserのidを取得できる。

prototypes/show.html.erb
<%= link_to comment.user.name, user_path(comment.user.id) %>

誰のコメントか表示されているところのコード。each文で|ブロックパラメーター(変数)にcomment|が入り、commentとアソシエーションしているuserのidを取得。

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

Ruby 標準入力と様々なメソッド

はいじめに

これは学習用のメモになります。

標準入力とは?

もともとはLINUXなどのUnix系OSで用意されていた仕組みです。
標準入力に対応するようにプログラムを作っておけば、プログラム実行時に、ファイルを読み込んだり、キーボードからデータを読み込んだり、パラメータを指定したりというように、入力先を切り替えることができます。

getsメソッド

標準入力から一行入力

line = gets
puts line

to_iメソッド

標準入力から一行読み込み、整数に変換

line = gets.to.i
puts line

//入力された数字が出力される
例題)
標準入力で2つの整数が2行で与えられます。
1つ目の数値から2つ目の数値までを、1ずつ増加させながら、1行ずつ順番に出力するプログラムを作成してください。たとえば、3と5という数値が与えられた場合、次のように出力します。

3
4
5
num1 = gets.to_i
num2 = gets.to_i
for i in num1..num2
    puts i
end

* to_sメソッド

データを文字列に変換する

* to_fメソッド

データを小数点付きに変換すること

chompメソッド

文字列の末尾の改行コードを取り除きます

line = gets.chomp
puts = "#{line}は、スライムを攻撃した"       //改行コードを取り除いた状態で出力される

splitメソッド

splitメソッドとは簡単に言うと文字列を分割して配列にするためのメソッドです。

str = "samurai engineer blog"

array = str.split

p array

[実行結果]

["samurai", "engineer", "blog"]

区切り文字を指定して分割する方法

文字列.split(区切り文字)

str = "samurai,engineer,blog"

array = str.split(",")

p array

[実行結果]

["samurai", "engineer", "blog"]

最後に

この他にも様々なメソッドあると思います。
日々更新していこうと思います。
また、間違っているところがあればご指摘していただけると幸いです。

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

Sikuli (SikuliX) + RubyでRPAことはじめ(何度もつまづいたけどちゃんとできたよ)

Rubyでもできると聞きまして

SikuliXというフリーのRPAツールがあります。(公式ドキュメント
Sikuliの後継にあたるものらしいのですが、上記公式に

SikuliX supports as scripting languages
Python language level 2.7 (supported by Jython)
running RobotFramework text-scripts is supported (see docs)
Ruby language level 1.9 and 2.0 (supported by JRuby)
JavaScript (supported by the Java Scripting Engine)

と、「Rubyでもコマンドが書けるぜ!」的なことが書いてあり、じゃあちょっとこれで遊んでみようかな、と思いました。1

しかし、Ruby+SikuliXの導入記事って少ないこともあり、やってみると結構最初の設定段階ではまりました。そこでこの記事では同じようなことをやってみたい方のために、この方法でできたよ!というご報告をさせていただきます。

(特に日本語の記事が少ないですね。この界隈、Pythonに比べて多勢に無勢と言った感じなので、もっと増えてー!)

なお、筆者の環境はWindows 10です。

まずはSikuliXのダウンロード

https://raiman.github.io/SikuliX1/downloads.html
にアクセスし、以下赤囲みにしてあるリンクから2つの.jarファイルをダウンロードします。2
SikuliX用の好きなフォルダを作って、2つを同じところに保存しましょう。

downloadsikulix.jpg

SikuliXが動くバージョンのJava (Open JDK)をダウンロード

Javaを既にインストールしている人はsikulixide-2.0.4.jarを動かしたくなるところですが、一旦我慢してください。2020年10月現在、最新のJavaではエラーになり、JRuby Interpreterだけ別の場所に移動するだけされてしまうようです(多分、移動すること自体は問題なく、以下の作業を続ければいいだけだとは思いますが、不安ならJRuby Interpreterをもう一度ダウンロードしてください)。

ここで、少し古いJavaをダウンロードしてきましょう。
https://jdk.java.net/archive/
SikuliXにはバージョン11.0.2がおすすめのようですので、探してダウンロードします。

download_openjdk.jpg
zipは展開し、できたフォルダは自分のわかるところに置いておきます。私はCドライブ直下にしました。

とりあえず起動してみる

コマンドプロンプトでSikuliXをダウンロードしたフォルダに移動し、以下のコマンドを入力します。※但し、これは私のようにCドライブ直下にOpenJDKフォルダを配置した場合です。別の場所に置いた方は、それに従ってjavaのパスを指定してください。

C:\openjdk-11.0.2_windows-x64_bin\jdk-11.0.2\bin\java -jar sikulixide-2.0.4.jar

こうすると、先ほどのようにJRuby Interpreterがどこかに移動するのは同じ事ですが、今度はエラーが出ずにIDEが立ち上がってくれます。
sikulixide1.jpg

しかし、これではまだRubyスクリプトは動きません。正確に言えばputs "Hello"ぐらいは動いてくれるのですが、肝心のSikulix用gemがJRubyにインストールできていないため、RPAらしいことはこのままではできないのです。

gem "sikuli"をJRubyにインストール

そこで、JRubyインタプリタにgemをインストールしたくなるわけですが、そもそも、先ほどダウンロードしたJRubyインタプリタはどこに行ってしまったのでしょう?探してみたところ、以下に移動されていました。

C:\Users\(ユーザー名)\AppData\Roaming\Sikulix\Extensions

jruby-complete-9.2.0.0.jarが発見できると思います(バージョン番号は多少違うかもしれません。)

あとはこれにgemをインストールすればいいことになります。私はJRubyのgemというのがよくわかっていないのですが、こちらのQ&Aを参考に、以下のようにしました。

  • まず、JRubyのjarと同じ場所に適当なフォルダを作ります(私は上記参考と同じbiojrubyという名前にしましたが、どんな名でも構わないと思います)。
  • そして、コマンドプロンプトでjrubyのjarと同じフォルダに入り、以下を実行ください。※jarのバージョンと作成したフォルダ名は適宜読み替えてください。
java -jar jruby-complete-9.2.0.0.jar -S gem install -i ./biojruby sikuli
jar uf jruby-complete-9.2.0.0.jar -C biojruby .

gem installで警告がいくつか出ますが、気にしなくてOKです。

確認してみましょう。

$ java -jar jruby-complete-9.2.9.9.jar -S gem list sikuli

これもいくつか警告が出ますが、バージョン番号とともにsikuliが表示されればOKです。

環境変数にSIKULI_HOME

最後の仕上げです!
Windowsの環境変数の設定で、SIKULI_HOMEを加えてあげます。
(どうやらgem sikuli側がSIKULIの.jarを認識するのに必要らしい?これもStack Overflowの受け売りです…ごめんなさい)

sikulihome.jpg

RPA実行

お待たせしました!いよいよ実行です。
コマンドプロンプトは開き直し、再度SikuliX側の.jarをOpenJDKで開きましょう。

C:\openjdk-11.0.2_windows-x64_bin\jdk-11.0.2\bin\java -jar sikulixide-2.0.4.jar

以下、画像で恐縮ですがスクリプト例を示します。この場合、

  • デスクトップ上のEdgeブラウザのアイコンをダブルクリック
  • 「新しいタブ」と同じ画像が表示された場合に、「abc」とキー入力する

という命令になっています。

sikuliide.jpg

アイコンなどはメニューの「スクリーンショットを撮る」などで簡単に挿入することができます。お好きなアイコンでお試しください。

完成したら、任意の場所に保存した後(.sikuliと付記されたフォルダが作られます)、実行ボタンをクリックすると…、

どうでしょう?ちゃんと動いたという方、おめでとうございます!

あとは

https://sikulix-2014.readthedocs.io/en/latest/toc.html
あたりを参考にしながら、適宜Rubyに読み替えをどうするか調べていくことになると思う…のですが、やっぱり情報が少ない…。
というわけで、役に立ちそうなトピックがあったら、私自身これからもQiitaで取り上げていこうと思います。

感想

今回、フリーのRPAとしてSikuliXを取り上げてみました。Rubyでできるというのがきっかけでしたが、結構環境構築だけで手間取ってしまいました。

参考にできる記事が少ないというのは置いておくとしても、仕事効率化のためのRPAにおいてこれはネックになるかもなあ、というのが正直のところです。今回は自宅で遊んでただけなのでいいですが、数十のPCに導入するだけでも結構手間になりそう。Jythonだとそんなこともないんでしょうか。

RubyでできるRPAなら、有償ですがRobowareといったものがあるらしく(いや、回し者じゃないですよ)、しっかり安定した動作を期待するならサポート面とか考えてもこういうところを検討したほうがいいってことになるかもですね。

むしろ、小さなチームでできることを考える場合はSikuliXが有効になってくるかもしれません。そこでJRubyが選択肢とする人が増えて、公式もドキュメントなどに力を入れてくれるようになるといいなあ、と、結局そこに行きつくのでした、


  1. 正確にはJRubyという、RubyスクリプトをJavaに変換して動作する環境を利用しています。SikuliXそのものはJavaで動いていますので。 

  2. Jython interpreterと両方ダウンロードしてもいいというようなことが書いてありますが、これをやるとJythonのほうが優先されてJRubyが動かせませんでした。この際は結局一度アンインストールしてやり直すしかありませんでした。なお、アンインストールについては https://auto-worker.com/blog/?p=473 を参照下さい。 

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

クラウドIDE:Ruby on RailsチュートリアルでHerokuがインストールできなかった件

Ruby on Railsのチュートリアルで、Herokuをインストールしようとしたら、以下のエラーが出て進まなかった件。
調べても何が悪いのかよくわからなかった(゚∀゚)。
bash: /home/ec2-user/.profile: Permission denied

実行コマンドはチュートリアルに記載してあった下記の通り。
source <(curl -sL https://cdn.learnenough.com/heroku_install)

環境

・AWS,Cloud9の統合環境(クラウド統合環境)

対処

解決方法をググっても特に見つからなかった\(^o^)/。

Heroku CLIをインストールするらしいので、Heroku CLIのインストール方法を確認した。
書いてあるコマンドで、チュートリアル以外のコマンドでインストールできないか調べた。
https://devcenter.heroku.com/articles/heroku-cli

下の方にnpmコマンドを使ってもインストールできることが書いてある。
インストールの注意文を見ると、「npm」と「node」が入っているのが前提のインストール方法なのだそうだ。

This installation method is required for users on ARM and BSD. You must have node and npm installed already.
npm install -g heroku

というわけでクラウドIDEに入っているかを確認した。
image.pngimage.png
どちらも入っているらしいので、ダメ元で記載されていたコマンドを使ったらインストールができた。
もし詰まっていたら、一度この方法を試してみてください。(役に立つかはわからないけど)

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

【Rails6】主なGemについて

はじめに

 Railsを使うにあたり、当たり前のようにpumaとかwebpackerとかlistenを使用していますが、正直あまり意味がわからずに使っていました。ここいらで主なGemについてまとめたいと思います。

Railsデフォルトの主なGem

 下記がRails6で生成されるデフォルトGemです。

Gemfile
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 6.0.3'
# Use sqlite3 as the database for Active Record
gem 'sqlite3', '~> 1.4'
# Use Puma as the app server
gem 'puma', '~> 4.1'
# Use SCSS for stylesheets
gem 'sass-rails', '>= 6'
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem 'webpacker', '~> 4.0'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use Active Model has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Active Storage variant
# gem 'image_processing', '~> 1.2'

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false

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 do
  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
  gem 'web-console', '>= 3.3.0'
  gem 'listen', '~> 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

group :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'
  # Easy installation and use of web drivers to run system tests with browsers
  gem 'webdrivers'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

 それぞれ説明していきます。

sqlite3

 SQLデータベースエンジンを実装するC言語ライブラリ。
 パスワード設定がない等のセキュリティ機能がないため、基本的に開発環境、テスト環境で使用し、本番環境では別のデーターベースエンジンを使用する。

https://www.sqlite.org/index.html

puma

 RubyWebアプリケーション用のHTTP1.1サーバー構築に使用する。スレッドプールを使用してリクエストを処理する。

https://puma.io/
https://github.com/puma/puma

sass-rails

 RailsでSass(SCSS)使用できる。

https://github.com/rails/sass-rails

webpacker

 Webアプリケーションで一般的に良く使われるメジャーな設定を、標準で実装してくれるwebpackのラッパー。webpackは、最新のJavaScriptアプリケーション用の静的モジュールバンドラー1

【主な機能】
 ・webpack 4.x.x
 ・複数のエントリポイント2を使用した自動コード分割
 ・新しいJavaScript構文(ES6)をブラウザで動くように変換
 ・React、Vue.js、PostCSSなどのモダンなフレームワークに対する豊富な実績

https://github.com/rails/webpacker

Jbuilder

 Jbuilderは、JSON構造を宣言するためのシンプルなDSL(ドメイン特化言語)を提供。
https://github.com/rails/jbuilder

turbolinks

 Webアプリケーションでのリンクの追跡が高速になる。
https://github.com/turbolinks/turbolinks-classic

bootsnap

 railsの起動時の処理を最適化する(パスとrubyのコンパイル結果をキャッシュ)ことで起動時間を短縮してくれる。
 ActiveSupport や YAML もサポートしている。

https://github.com/Shopify/bootsnap/blob/master/README.jp.md

byebug

 デバッグツール。

https://github.com/deivid-rodriguez/byebug

web-console

 View 内でコンソールを立ち上げて、変数や parameter などの状態を見る事の出来るデバック用のライブラリ。エラー箇所のデバッグがしやすくなる。

https://github.com/rails/web-console

listen

 ファイルの変更を検知してそれをフックに何か処理ができる。

【主な機能】
・ファイルの変更、追加、削除を検出
・複数のディレクトリを監視

https://github.com/guard/listen

spring

 Railsアプリケーションのプリローダー3。アプリケーションをバックグラウンドで実行し続けることで開発をスピードアップするため、テスト、移行を実行するたびにアプリケーションを起動する必要がなくなる。

https://github.com/rails/spring

spring-watcher-listen

 Springはファイルシステムをポーリング4するのではなく、Listenを使用してファイルシステムの変更を監視します。

https://github.com/jonleighton/spring-watcher-listen/

capybara

 実際のユーザーがアプリをどのように操作するかをシミュレートすることにより、Webアプリケーションのテストを支援する。

https://github.com/teamcapybara/capybara
https://en.wikipedia.org/wiki/Capybara_(software)

selenium-webdriver

 capybaraではJavaScriptをサポートしていないため、selenium-webdriverでシュミレートする。

https://github.com/SeleniumHQ/selenium/tree/trunk/rb

webdrivers

  webブラウザを外部のソフトウェアから操作したり情報を取得したりできるようにするためのものです。

https://github.com/titusfortner/webdrivers

tzinfo-data

 Windows ではタイムゾーン情報用に使用する。UnixベースのOSではtzinfoからシステムのタイムゾーン情報に直接アクセスできるので使用する必要はない。

https://github.com/tzinfo/tzinfo-data

それ以外の主なGem

bcrypt

 パスワードを暗号化できる。

https://github.com/codahale/bcrypt-ruby

devise

 Webアプリケーションには必須の、ユーザー認証機能を作ることができる。会員登録用フォームを作成や、メールやFacebook等での認証も実装できる。

https://github.com/heartcombo/devise

kaminari

 ページネーション機能を簡単に追加できる。

https://github.com/kaminari/kaminari

carrierwave

 画像のアップロード機能。

https://github.com/carrierwaveuploader/carrierwave

active admin

 CRUD系の管理画面を作成することができる。

https://github.com/activeadmin/activeadmin

ruboCop

 コーディング規約どおりに書かれているかをチェックする静的コード解析ツール。

https://github.com/rubocop-hq/rubocop

pry-rails

 Rubyのirbのようにrailsのコンソールでメソッドなどを使えることができるようになる。

https://github.com/rweng/pry-rails

faker

 偽のデータを生成する。

https://github.com/faker-ruby/faker

capistrano

 自動デプロイツール。

https://github.com/capistrano/capistrano

まとめ

 Railsは歴史が長い分、Gemは数が多いですね。初学者だと選定が難しそうです。まずは開発前にどんな機能がどこまで必要なのかピックアップしてGemを選定する必要がありそうです。Gemを選定の参考になりそうなサイトを張っておきます。この記事も有用なGemがあればどんどん更新していこうと思います。
 Gemの探しツール
 Gemのランキングサイト


  1. モジュールごとに分割され、別々になったJavaScriptファイルの依存関係を解決して、1つのファイルにまとめるツール。 

  2. プログラムを実行を開始する場所のこと。(エントリーポイント - wiki

  3. サイトを表示する前に予め画像やコンテンツを先読みしてキャッシュする事。(参考→プリローダーとは

  4. 主に通信などの競合を回避するために、ホスト側が各機器に対して定期的に問い合わせを行い、条件を満たした場合に送受信や各種処理を行うこと。(ポーリング - wiki

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

動画を挿入する方法

トップページなどに動画を挿入する方法

GIF

https://gyazo.com/3015a8b1f689153dcfe7fcb308d483bb

6582025a04cb61be20a4cabc8a556419.png

下記コード

index.html
<div class="bg-video-wrap">
  <p>Brilliant Blue</p>
  <video src="images/foreign.mp4" autoplay loop muted>
  </video>
</div>
css
.bg-video-wrap {
  position: relative;
}
p {
  font-family: serif;          
  color: #fff;
  font-size: 400%;
  position: absolute;
  left: 30%;
  top: 100px;
  z-index: 1;
}

以上です!

状況に応じてCSSは各自変更して使って下さい!

foreign.mp4の部分は各々、ダウンロードや作った動画の名前を決めると思うのでそれを当てはめて下さい!おそらく末尾はmp4でもmovでもどちらでも挿入できます!

現場からは以上です!

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

RubyでDIしてみた

DIとは

dependency injection (依存性注入)のこと

外からconstractorにobject渡せるようにすることで依存を避けようっていうもの

実装

Interactorを例に書いていく
dry-containerdry-auto_injectを使ってDIしてみた

DIしてない版

not_di.rb
module Interactor
  class FindUser
    def initialize(params)
      @params = params
    end

    def call
      user_repo = UserRepository.new
      user_repo.by_pk(@params[:id])
    end
  end
end

UserRepositoryに依存しているのがわかる

dry-container使わない版

pure-di.rb
module Interactor
  class FindUser
    def initialize(params: {}, user_repo: UserRepository.new)
      @params = params
      @user_repo = user_repo
    end

    def call
      @user_repo.by_pk(@params[:id])
    end
  end
end

initializdの引数がでっかくなってみづらいコードになりがち
これありますねぇ

dry-container版

dry-di.rb
module Container
  module FindUser
    extend Dry::Container::Mixin

    # 注入するobjectを登録していく
    # blockでdefaultのobjectを宣言できる
    register "params"

    register "user_repo" do
      UserRepository.new
    end
  end
end


module Interactor
  class FindUser
    Import = Dry::AutoInject(Container::FindUser)
    # includeしたものをmethodで呼び出せるようになる
    include Import["params", "user_repo"]

    def call
      user_repo.by_pk(params[:id])
    end
  end
end

コードは増えたけど責務を切り分けることで見通しがよくなった

InteractorがContainerに依存していて、呼び出す人(Controllerとか)もContainerに依存しているので
いい感じに依存性を逆転させることができた

before: controller -> interactor
after: controller -> container <- interactor

何が嬉しいのか

  • コードがスッキリする

initializeをcontainerに委託することでinteractorの責務が減った
classあたりの責務は減らしていきたい

  • 単体テストが簡単にできる

InteractorのテストをDB用意しないでできる
上の例だと by_pk(id) に反応するobject渡せば良い

test.rb
User = Struct.new(:id, :name, :age)

class TestUserRepo
  def by_pk(id)
    User.new(id, "tarou", 20)
  end
end

params = { id: 1 }
user_repo = TestUserRepo.new
input = { params: params, user_repo: user_repo }

Interactor::FindUser.new(input).call

みたいにできる

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

ターミナルって何?

ターミナル説明

ターミナルは、PCに命令をすることができるツールですね。
環境構築を実行するには コマンドラインというツールが必要となります。
Macにデフォルトでインストールされているコマンドラインがターミナルです。

コマンドライン説明

コマンドライン(または コマンドラインインタフェース:CLI )とは、コンピュータに対してキーボードからコマンドという文字を打ち込んで操作を行うツールになります。

GUI(グラフィカルユーザインターフェース)

コマンドラインとは対照的に、グラフィックを用いて操作を行う仕組みを グラフィカルユーザインタフェース(GUI) と言います。

例えば、「マウスでファイルをダブルクリックして開く」という操作など、普段PCで行う操作のほとんどはGUIで行なっていることが多いです。

基本.png

ターミナルを使う理由を学ぼう

先程、マウスなどを使った直感的に操作をすることができるGUIと、ターミナルのようにコマンドを使用して操作するCLIがあることを説明させて頂きました。

直感的に操作ができて普段から使い慣れたGUIではなく、なぜわざわざターミナルの操作に慣れ、それを使用しなければならないのでしょう?

その理由は、GUIとCLIでは CLIの方が行える操作の数が圧倒的に多いためです。

GUIのように全ての操作をグラフィカルに表現していては画面がメニューやボタンで溢りかえってしまって、分かりづらくなってしまうためです。
普段行わない操作についてはボタンが無かったり、そもそも簡単に操作できては危険なものであれば操作できないものである場合が多いです。

ボタン.png

CLIでは、PCに対しての操作のほとんどが行えると言って良いほどに、
様々な操作を行うことができます。

「Rubyプログラムの実行」もCLIが行える操作の1つです。
そのため、 ターミナルの操作は必須スキルだと思います。

ターミナルの見方説明

まずは、ターミナルを開いて表示画面を見てみましょう!!

ターミナル見方.png

コンピュータ名は自分のコンピュータの名前ですね。
ここでは「A-137」となっています。ユーザ名は自分の名前です。
ここでは「div-M.T」となっています。このあとに続く「$」または「%」は プロンプト といい、 コンピュータが命令を受け付けられる状態であることを示します 。つまり今は、命令待ちの状態です。

まとめ

・ターミナルとは、PCに命令をすることができるツールのこと
・「〜」には、カレントディレクトリが入ります。
 「%」は、プロンプトを言います。

プログラミングは、膨大な量の情報があるので、
基本から徐々に焦らずに理解していきましょう!!
できるようになるまで時間は、かかります。
でも、毎日継続して行えばだんだんとできるようになるので
一緒に頑張りましょう!!

以上。

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

マイグレーションファイルから Model を直接触るのはやめて欲しい

何が起こったのか

開発環境で新たにDBを作成。

お約束通り、

$ rails db:create
$ rails db:migrate

大量のマイグレーションファイルを一気に migrate しようとしたその時、事件は起こった。

PG undefined column hogehoge ...

的な感じで、スッと通らない?
嫌な予感がする。

なになに。。。。

マイグレーションファイルから直接 Model にアクセスしていたんです。

migartion_file
...

Hoge.delete_all

...

みたいな感じでモデルに対して直接操作を行う処理が書かれていた?

そのマイグレーションファイルは過去のものであり、その当時はそのモデルがあったのだろうが、今現在のソースコードには存在しない。
当然、 そんなモデルは無いよ と怒られたりする。

こんな感じで直接 Model に対して処理を行っているマイグレーションファイルが散見された。
結果 migrate を通すのにものすごく苦労し、過去のマイグレーションファイルにも手を入れなくてはいけなくなった。

マイグレーションファイルってそういうものなのでしょうか?
いいえ、違うはずです。

どうすれば良いのか

マイグレーションファイルは、後々どんな人が開発に入ってきてもしっかり rails db:migrate が通るように積み上げていきましょう。

ファイル数が増えるのは仕方ありません。大事なのはしっかり通るかどうかです。
おそらく当時の開発者もモデルに対して処理を書いているのは分かっていたはずで、データメンテナンスがしたかったのだと思います。

既存データのメンテナンスはマイグレーションファイルでやらないで、rake task を作成するとか他にもやり方があったはずです。

自戒も込めて、マイグレーションファイルに関して気をつけるべきことを書いておきます

  • マイグレーションファイルを追加した時には、 rails db:migrate:redo を習慣づけて redo が通ることを確認する
  • 古いマイグレーションファイルを後から書き換えない(見栄えは悪いが単純に積み上げていくのがベストプラクティス)
  • データメンテナンスはマイグレーションファイルの中で行わない(rake task を作ったり rails console から手動やったり他の方法がある)

Model は単なる ruby のクラスに過ぎません。
それは今後名前が変わるかもしれませんし、データの持ち方も変わるかもしれません。

マイグレーションファイルの中からモデルを直接触るのはやめましょう?


どなたかの役に立てば幸いです。

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

deviseのコントローラー初期状態まとめ

deviseのコントローラーの初期状態が気になったことがあり、一度サンプルを作る羽目になったのでメモとしてQIITAに残しとく。

confirmations_controller.rb
class Sample::ConfirmationsController < Devise::ConfirmationsController
  # GET /resource/confirmation/new
  # def new
  #   super
  # end

  # POST /resource/confirmation
  # def create
  #   super
  # end

  # GET /resource/confirmation?confirmation_token=abcdef
  # def show
  #   super
  # end

  # protected

  # The path used after resending confirmation instructions.
  # def after_resending_confirmation_instructions_path_for(resource_name)
  #   super(resource_name)
  # end

  # The path used after confirmation.
  # def after_confirmation_path_for(resource_name, resource)
  #   super(resource_name, resource)
  # end
end
omniauth_callbacks_controller.rb
class Sample::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  # You should configure your model like this:
  # devise :omniauthable, omniauth_providers: [:twitter]

  # You should also create an action method in this controller like this:
  # def twitter
  # end

  # More info at:
  # https://github.com/heartcombo/devise#omniauth

  # GET|POST /resource/auth/twitter
  # def passthru
  #   super
  # end

  # GET|POST /users/auth/twitter/callback
  # def failure
  #   super
  # end

  # protected

  # The path used when OmniAuth fails
  # def after_omniauth_failure_path_for(scope)
  #   super(scope)
  # end
end

passwords_controller.rb
class Sample::PasswordsController < Devise::PasswordsController
  # GET /resource/password/new
  # def new
  #   super
  # end

  # POST /resource/password
  # def create
  #   super
  # end

  # GET /resource/password/edit?reset_password_token=abcdef
  # def edit
  #   super
  # end

  # PUT /resource/password
  # def update
  #   super
  # end

  # protected

  # def after_resetting_password_path_for(resource)
  #   super(resource)
  # end

  # The path used after sending reset password instructions
  # def after_sending_reset_password_instructions_path_for(resource_name)
  #   super(resource_name)
  # end
end

registrations_controller.rb
class Sample::RegistrationsController < Devise::RegistrationsController
  # before_action :configure_sign_up_params, only: [:create]
  # before_action :configure_account_update_params, only: [:update]

  # GET /resource/sign_up
  # def new
  #   super
  # end

  # POST /resource
  # def create
  #   super
  # end

  # GET /resource/edit
  # def edit
  #   super
  # end

  # PUT /resource
  # def update
  #   super
  # end

  # DELETE /resource
  # def destroy
  #   super
  # end

  # GET /resource/cancel
  # Forces the session data which is usually expired after sign
  # in to be expired now. This is useful if the user wants to
  # cancel oauth signing in/up in the middle of the process,
  # removing all OAuth session data.
  # def cancel
  #   super
  # end

  # protected

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_sign_up_params
  #   devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
  # end

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_account_update_params
  #   devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
  # end

  # The path used after sign up.
  # def after_sign_up_path_for(resource)
  #   super(resource)
  # end

  # The path used after sign up for inactive accounts.
  # def after_inactive_sign_up_path_for(resource)
  #   super(resource)
  # end
end

sessions_controller.rb
class Sample::SessionsController < Devise::SessionsController
  # before_action :configure_sign_in_params, only: [:create]

  # GET /resource/sign_in
  # def new
  #   super
  # end

  # POST /resource/sign_in
  # def create
  #   super
  # end

  # DELETE /resource/sign_out
  # def destroy
  #   super
  # end

  # protected

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_sign_in_params
  #   devise_parameter_sanitizer.permit(:sign_in, keys: [:attribute])
  # end
end

unlocks_controller.rb
class Sample::UnlocksController < Devise::UnlocksController
  # GET /resource/unlock/new
  # def new
  #   super
  # end

  # POST /resource/unlock
  # def create
  #   super
  # end

  # GET /resource/unlock?unlock_token=abcdef
  # def show
  #   super
  # end

  # protected

  # The path used after sending unlock password instructions
  # def after_sending_unlock_instructions_path_for(resource)
  #   super(resource)
  # end

  # The path used after unlocking the resource
  # def after_unlock_path_for(resource)
  #   super(resource)
  # end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Rails 教わった事 その2

この記事の目的

プログラミングスクールで私が約3ヶ月で教わった事を転職活動に生かすためにまとめました。
私による私のためだけの記事です。

この講義で印象に残った一言は、
RSpecは絶対に身に付けなければいけない必須の技術。

Hamlで書く

Hamlを使うと閉じタグがいらなくなり、コードを減らせる。
Hamlの書き方

モデルメソッド

何回も使うロジックはモデルメソッドにする、DRYにする↓

example.html.haml
-  - if current_user.id == post.user_id #直書き
+  - post.created_user?(current_user)
    %p あなたは投稿した人です
post.rb
  def created_user?(user)
    self.user_id == user.id
  end

モデルメソッドをRSpecでテスト

本来はテストを書き、失敗を確認しながらメソッドを書く(TDD、テスト駆動開発)

post_spec.rb
  describe "#created_user?" do
    let(:user) { FactoryBot.create(:user) } #本人
    let(:other_user) { FactoryBot.create(:user) } #他人
    let(:post) { FactoryBot.create(:post, user: user) } #本人の投稿

    context "ログインユーザーと同じユーザーの場合" do
      it "trueを返すこと" do
        expect(post.created_user?(user)).to eq true 
      end
    end
    context "ログインユーザーと同じユーザーではない場合" do
      it "falseを返すこと" do
        expect(post.created_user?(other_user)).to eq false 
      end
    end 
  end

参考書

Everyday Rails - RSpecによるRailsテスト入門
とにかく手を動かして少しだけできるようになりました。
個人的にsystemスペックのajax関連のテストがトラウマです。

おすすめされた動画


TDD Boot Camp 2020 Online #1 基調講演/ライブコーディング

感想

今回は主にRSpecのライブコーディングを見せてもらった。
当時、あまりテストの必要性を感じなかったが、今回過去の動画を見返しながら気になったコードをリファクタリングしようとしたところ無意識に、RSpecを動かしていたので少しずつ慣れてきているのだと思う。

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

gemfile書き換えによるBundler::Dsl::DSLErrorの対処法

gemfileを書き換えた際のエラー対処法について後発者のためにメモを残しておく。

開発環境

windows 10 home
ubuntu 20.04 LTS
ruby 2.7.1
Rails 6.0.3
postgresql 11

エラー文

$ bundle
~~~~~中略~~~~~
 Permission denied @ rb_sysopen - /home/admin0/taskleaf2/Gemfile (Errno::EACCES)
~~~~~中略~~~~~
was an error while trying to read from `/home/admin0/taskleaf2/Gemfile`. It is likely that you need to grant read permissions for that path. (Bundler::PermissionError)
~~~~~中略~~~~~
 Bundler::Dsl::DSLError

今回は特に理由はないが3つ目のエラー文着目に着目して検索したところ以下のサイトに良さげな解決方法が記載されていたので実行してみる
https://stackoverflow.com/questions/57926553/bundle-install-gives-bundlerdsldslerror

$ chmod 644 Gemfile

これでエラーが発生しなくなったため、問題解決したと判断する。

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

並列処理入門 + Rubyでの新しい並列実行単位Ractor

この記事では、並列処理に関する入門的知識を解説する。
さらに、Rubyで開発されている新しい並列実行単位Ractorにも言及する。

まず、この話題をする上で混同しがちな用語についてまとめる。

並列処理(parallel)と並行処理(concurrent)について

並列処理 では、ある瞬間に複数の処理が同時に走る。
並行処理 では、複数の処理を時分割で順に処理する。並列処理とは異なり、ある瞬間に同時に走る処理は1つだけ。

ある複数の処理が実行されているタイミングを時系列で示すと、下図のようなイメージになる。
(青い線がある部分のみ処理が実行される)
image.png

この記事では並列処理の動作について扱うが、並列処理のコードを書いても結局並行処理のように動いている場合もあることには注意。
(例えば、1コアのCPUでは2つ以上の処理を並列に動作させることはできない、など。)
この辺りはOSやVMなどが良い感じにスケジューリングしてくれている。

マルチプロセスとマルチスレッド

一般に並列処理を実現するための主な方法として、 マルチプロセスマルチスレッド の2つがある。
マルチプロセスは、複数のプロセスを作成し、それぞれのプロセスで1つずつ処理を実行させる方法。
マルチスレッドは、1つのプロセス内に複数のスレッドを作成し、それぞれのスレッドで1つずつ処理を実行させる方法。

マルチプロセスの場合は、メモリ空間が各プロセスで分離されている。
このため、プロセス間で変数の受け渡しなどは基本的にできない。
また、それが故にプロセス間でメモリを介した意図しない相互作用は起こり得ないので、安全性は高い。
デメリットとしては、プロセスごとにメモリ空間を持つので、合計のメモリ使用量は増大しがち。(ただし、linuxではCopy on writeという仕組みにより、プロセス間のメモリを可能な限り共有してくれる。)

マルチスレッドの場合は、1つのプロセスが複数のスレッドを持つため、メモリ空間はスレッド間で共有される。
そのため、メモリ使用量は抑えられる上、実装によってはスレッドの作成や切り替えが、プロセスの作成や切り替えよりも軽いというメリットもある。
ただし、メモリを介してスレッド間が影響を及ぼしあうことができるため、データ競合などのバグは発生しがち。
一般に、マルチスレッドプログラミングは考慮すべきことが多く、正しく実装するのが難しいとされている。

なお、並列処理において1つの処理が実行される単位を 並列実行単位 と呼ぶ。
マルチプロセスの場合は並列実行単位はプロセスであり、マルチスレッドの場合はスレッドである。

(補足) スレッド処理の実現方法について

スレッド処理の主な実現方法として、 ネイティブスレッドグリーンスレッド の2種類がある。
ネイティブスレッドは、OSの実装をそのまま利用して、マルチスレッド処理を実現する方法。
スレッドのスケジューリング(今どのスレッドの処理を実行するか決めること)をOSに任せられるので処理系の実装は単純になる。
一方で、スレッドの作成や切り替え(いわゆるコンテキストスイッチ)の処理が重いというデメリットもある。
(ちなみにネイティブスレッドは、正確にはカーネルスレッドと軽量プロセスを合わせた概念とのことだが、詳細は割愛。ネイティブスレッドとカーネルスレッドが混用されることも多々ある気がする。)

グリーンスレッドは、言語処理系の仮想マシン(たとえば、crubyのyarv、javaのjvmなど)で独自に実装したスレッドで、マルチスレッド処理を実現する方法である。
golangのgoroutineもグリーンスレッドの一種で、その動作の軽快さはあまりにも有名。
crubyでは、1.9以前はグリーンスレッドにより実装されていたが、今はネイティブスレッドを利用する形に変更された。
グリーンスレッドはユーザースレッドとも呼ばれる。

マルチスレッド・マルチプロセスのコード例

一例として、Rubyにおける並列処理の実装を示す。
RubyではParallelというgemを用いることで、容易に並列処理を記述することが可能。

マルチプロセスのコードは下記のようになる。

multi_process.rb
require 'parallel'

Parallel.each(1..10, in_processes: 10) do |i|
  sleep 10
  puts i
end

このコードを実行して、プロセスリストを見ると、下記のようになる。
1個のメインプロセスと、10個の子プロセスが生じていることが分かる。

$ ps aux | grep ruby
  PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND PRI     STIME     UTIME
79050   9.7  0.1  4355568  14056 s005  S+    2:39PM   0:00.28 ruby mp.rb
79072   0.0  0.0  4334968   1228 s005  S+    2:39PM   0:00.00 ruby mp.rb
79071   0.0  0.0  4334968   1220 s005  S+    2:39PM   0:00.00 ruby mp.rb
79070   0.0  0.0  4334968   1244 s005  S+    2:39PM   0:00.00 ruby mp.rb
79069   0.0  0.0  4334968   1244 s005  S+    2:39PM   0:00.00 ruby mp.rb
79068   0.0  0.0  4334968   1172 s005  S+    2:39PM   0:00.00 ruby mp.rb
79067   0.0  0.0  4334968   1180 s005  S+    2:39PM   0:00.00 ruby mp.rb
79066   0.0  0.0  4334968   1208 s005  S+    2:39PM   0:00.00 ruby mp.rb
79065   0.0  0.0  4334968   1252 s005  S+    2:39PM   0:00.00 ruby mp.rb
79064   0.0  0.0  4334968   1168 s005  S+    2:39PM   0:00.00 ruby mp.rb
79063   0.0  0.0  4334968   1168 s005  S+    2:39PM   0:00.00 ruby mp.rb

マルチスレッドのコードは下記のようになる。

multi_threads.rb
require 'parallel'

Parallel.each(1..10, in_threads: 10) do |i|
  sleep 10
  puts i
end

こちらも同様にスレッド一覧を見てみる。

ps コマンドに -L をつけると、スレッドがプロセスのように表示される。
-L なしではプロセスが1個なのに対し、 -L をつけると11行表示される。
また、 NLWP カラムはプロセスのスレッド数を示すが、これが11(メインスレッドx1 + ワーカースレッドx10)となっていることからも、マルチスレッド処理になっていることが分かる。

$ ps aux | grep mt.rb
 4419  1.0  0.6 850176 12384 pts/1    Sl+  15:41   0:00 ruby mt.rb
$ ps aux -L | grep mt.rb
  PID   LWP %CPU NLWP %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
 4419  4419  6.0   11  0.6 850176 12384 pts/1    Sl+  15:41   0:00 ruby mt.rb
 4419  4453  0.0   11  0.6 850176 12384 pts/1    Sl+  15:41   0:00 ruby mt.rb
 4419  4454  0.0   11  0.6 850176 12384 pts/1    Sl+  15:41   0:00 ruby mt.rb
 4419  4455  0.0   11  0.6 850176 12384 pts/1    Sl+  15:41   0:00 ruby mt.rb
 4419  4456  0.0   11  0.6 850176 12384 pts/1    Sl+  15:41   0:00 ruby mt.rb
 4419  4457  0.0   11  0.6 850176 12384 pts/1    Sl+  15:41   0:00 ruby mt.rb
 4419  4458  0.0   11  0.6 850176 12384 pts/1    Sl+  15:41   0:00 ruby mt.rb
 4419  4460  0.0   11  0.6 850176 12384 pts/1    Sl+  15:41   0:00 ruby mt.rb
 4419  4461  0.0   11  0.6 850176 12384 pts/1    Sl+  15:41   0:00 ruby mt.rb
 4419  4462  0.0   11  0.6 850176 12384 pts/1    Sl+  15:41   0:00 ruby mt.rb
 4419  4463  0.0   11  0.6 850176 12384 pts/1    Sl+  15:41   0:00 ruby mt.rb

マルチスレッドプログラミングの難しさ

マルチスレッド処理においては、複数のスレッドがメモリを共有した状態で処理が並列に実行されるため、様々な問題が発生しうる。
主な問題の一つがデータレースである。

データレースは、下記のようなコードで発生する可能性がある。
このコードは 1~10までの整数の和を求めようとしたものであるが、データレースの問題があるために、正しく和を求められない可能性がある。

require 'parallel'

sum = 0;

Parallel.each(1..10, in_threads: 10) do |i|
  add = sum + i
  sum = add
end

puts sum

このコードにおいて、各スレッドは変数 sum を共有しており、各スレッドがsumの読み込みや書き込みを同時に行う。
この結果、あるスレッドで書き込まれた内容が、別のスレッドにより上書きされる可能性がある。
このため、上記のコードは正常に和を計算できない可能性があるという問題がある。

データレースの問題を解決する一般的な方法は、スレッド間で排他ロックを取る方法である。

require 'parallel'

sum = 0;
m = Mutex.new

Parallel.each(1..10, in_threads: 10) do |i|
  m.lock
  add = sum + i
  sum = add
  m.unlock
end

puts sum

これにより、ロックを取っている間の処理は同時に1スレッドしか実行されなくなり、データレースは解消される。

これらの問題が正しく考慮され、マルチスレッドにおいても正常に動作するコードを、スレッドセーフと呼ぶ。

Global Interpreter Lockについて

軽量言語(ruby, python等)でのマルチスレッド処理においてしばしば話題に上がるのが GIL である。
ちなみにRubyにおいてはGVL(Giant VM Lock)と呼ばれている。

GILは、スレッド同士の排他制御を行うことで、複数のスレッドが同時に実行されることを防ぐ。
つまり、一つのインタープリタ、VM内では同時に一つのスレッドしか実行されないようにする。
これが必要な理由やメリットとして、下記が挙げられる:

  1. マルチスレッドプログラミングをする際、個々のデータ構造ごとに排他処理を記述する必要がなくなる
    • 処理が高速化する
    • アプリ開発者のコーディングは簡単になる(データレースについて考えることが減る)
  2. ネイティブプラグインの実装がスレッドセーフでないことが多々あるが、それらの実装を変えずに安全に実行するため
  3. VMの実装自体がスレッドセーフではない

GILのおかげで、実は先程例示したマルチスレッドプログラミングのRubyコードは、Mutexを利用しない場合でも正常に動作する。
この挙動は、プログラミングを楽にするというRubyの根本思想に違わないものと言える。

しかしながら、同時に1つのスレッドしか実行できないということは、本来の並列処理が不可能であることを意味する。
RubyやPythonが並列計算に適していないとしばしば言及されるのは、このためである。

なお、例外的にI/Oの待機時はスレッドはGILを解除するため、実質的に複数のスレッドが同時に処理を実行できる。
このため、I/O待ちが多いような処理(ウェブサーバーなど)では、GILのある処理系においても実用的にマルチスレッドが利用される。

実装の例

HTTPサーバーは通常リクエストごとに同時に処理する必要があるため、並列処理の実装がなされていることが多い。
Rubyにおける代表的なHTTPサーバーとしてunicornpumaがあり、前者はマルチプロセスの実装、後者はマルチスレッドの実装である。

unicornとpumaはこちらのブログで性能が比較されている。

このブログの結論は、下記のようなものである:

  • CPU boundな処理においては、unicornがやや優れたパフォーマンスを発揮する
  • I/O boundな処理においては、pumaのほうが圧倒的に優れたパフォーマンスを発揮する

image.png
image.png
出典

これは、上記の仕組みを考えても、納得のいく結果である。

Ractorについて

ここまで並列処理の実現方法について説明し、Rubyでの実装やその性能を示した。
Rubyにおけるマルチスレッド処理は、GVLによって、本来のパフォーマンスを発揮することができないという問題がある。
Ractor(旧称: Guild)は、この問題を解決するために生まれた新しいRubyの並列処理機構である。

Ractorは従来のGVLによるマルチスレッドプログラミングが扱いやすくなるという利点を保ちながら、本来のマルチスレッドのパフォーマンスを実現できる。

その仕組みを解説する。

Ractorの思想

データレースが生じるのは、スレッド間がメモリを共有しているために、複数のスレッドが一つの変数に対して読み書き可能なためである。
これを解決する方法としては、

  1. 全ての変数を読み取り専用(Immutable)にする
  2. スレッド間で共有する変数は型で明示し、スレッドセーフでない処理に対してコンパイル時に検知する
  3. 並列実行単位ごとにメモリを独立させる

Ractorでは3の方法が採られた。この新しい並列実行単位がRactorと呼ばれている。
1つのRubyプロセスは1つ以上のRactorを持ち、1つのRactorは1つ以上のスレッドを持つ。
Ractorはそれぞれ個別のメモリ空間上で動作するため、従来のスレッドのようにメモリを共有することによる問題は生じない。

image.png
出典: https://www.slideshare.net/KoichiSasada/guild-prototype

また、Ractor導入以前のRubyコードは、1つのRactor内で動かすことにより後方互換性を保つことができる。

Ractor間でのデータ共有の方法

Ractor同士はメモリを共有しないため、情報の受け渡しが面倒になると思うかもしれない。
これを解決するため、Ractor間の通信を実現する channel という機能も用意されている。
共有したいオブジェクトは channel を経由してのみ受け渡すことができる。

オブジェクトは 共有可能オブジェクト共有不可オブジェクト に分類される。

共有可能オブジェクトは、読み取り専用の定数など、Ractor間で共有してもデータレースの発生し得ないオブジェクトを指す。
共有可能オブジェクトは、 channel を通じて自由に共有できる。

共有不可オブジェクトは、一般のミュータブルなオブジェクトを指す。
このオブジェクトを channel に通すと、ディープコピーまたはムーブセマンティクスが発生する。
ディープコピーの場合は、コピーの処理コストやメモリ使用量の増大が発生するが、マルチプロセスと同様の安全さ・わかりやすさがある。
ムーブセマンティクスの場合は、オブジェクトの所有権が別のRactorに譲渡される形になる。
このため、元のRactorからはそのオブジェクトを参照できなくなるが、ディープコピーとは異なりコピーほどの処理コストはメモリ使用量も増大しない。

まとめると:

  • 基本的にRactor間でメモリを共有しない
  • 必要なオブジェクトのみを開発者が明示的に指定して共有する
  • 共有する場合は、オブジェクトに応じて最適な方法を選択する

ことで、Ractorはスレッドセーフ性を保ちながらも容易なマルチスレッドプログラミングを実現する。

Ractorはプロセスとスレッドの中間に位置する並列実行単位である。
開発者がRactor間で共有したい情報を適切に選択することで、マルチプロセスほどRAM使用量を増やさず、またマルチスレッドのようにGILによるパフォーマンス低下がなく、並列処理を実現できる。

Ractorの現在

RactorはRuby 3の新機能として注目を浴びている。
Ractor自体は今も開発が進んでいるようで、一般Rubyユーザーの手に届くのはもう少し先になるだろう。
今後はRubyのマルチスレッド処理をするライブラリがRactorで再実装されることも期待される。
Pumaに代わるHTTPサーバーが主流になる時代も近い、かもしれない。

参考文献

勉強のためまとめた文章です。間違いがあればご指摘いただければ幸いです!

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

【Rails】SessionsHelperを使ってコントローラー間でパラメータを受け継ぐ

はじめに

某プログラミングスクールでフリマアプリを作成中、購入内容の確認ページからクレジットカード登録ページに遷移。カード登録後、購入内容の確認ページに遷移することが出来なくなった。

原因:コントローラーがitems_controller.rbからcredit_cards_controller.rbに変わった時点でパラメータの:item_idが0になっていた。

詳細

  1. ユーザーログイン後、routingでトップページ(items#index)へ遷移。
  2. 他ユーザーの出品商品(items#create)の詳細ページ(items#show)へ遷移。
  3. 詳細ページ(items#show)の購入ボタンより商品購入ページ(items#purchase)へ遷移。
  4. クレジットカード情報の登録・変更するために、登録・変更ボタンより(credit_cards#index)へ遷移。
    1. 遷移したが変更がなければ、もどるボタンで商品購入ページ(items#purchase)へ遷移。
    2. 変更・未登録ならカード登録ページ(credit_cards#new,create)に遷移後,(credit_cards#show)に遷移。その後カード決定・もどるボタンより商品購入ページ(items#purchase)へ遷移。
  resources :items, only: [:index, :new, :create, :show, :edit, :destroy] do
    get '/purchase/:id', to: 'items#purchase', as: :purchase
  end
  resources :credit_cards, only: [:index, :new, :create, :show, :destroy]

スクリーンショット 2020-10-11 13.37.34.png

5. 4-1,4-2にてitem_idがないというErrorが発生した

スクリーンショット 2020-10-11 13.58.34.png

ターミナルで確認するとitem_idがcontrollerを跨ぐタイミングで消えている。

Processing by ItemsController#purchase as HTML
  Parameters: {"item_id"=>"2"}
  Item Load (0.3ms)  SELECT  `items`.* FROM `items` WHERE `items`.`id` = 2 LIMIT 1
Processing by CreditCardsController#show as HTML

実装内容

item_idを引き継ぐ方法を検索していたところ...

【Rails】Sessionの使い方について
Ruby on Rails チュートリアル - セッション

sessionが使えそうだと判断。
はじめにapplication_controller.rbにinclude SessionHelperを追加。

application_controller.rb
class ApplicationController < ActionController::Base
  include SessionsHelper #左記を追加
end

/app/helpersに/sessions_helper.rbを作成し、下記コードでSessionsHelperを呼び出し

/app/helpers/sessions_helper.rb
module SessionsHelper
end

参考記事
「初期化されていない定数ApplicationController :: SessionsHelper(NameError)」が表示されるのはなぜですか?

これでsessionの準備が完了

まず、sessionにitem_idを保存する。

app/controllers/items_controller.rb
def purchase
  @item = Item.find(params[:item_id]) #paramsからitem_idを取り出し
  session[:item_id] = @item #取り出したitem_idをsessionに保存
end

次に、credit_card登録先のcontroller(credit_cards#index,credit_cards#show)でsessionに保存していたパラメータを取り出す。
binding.pryで確認したところ配列になっていたので、valuesで取り出す。

app/controllers/credit_cards_controller.rb
#session[:item_id]で先ほどのsessionデータを取り出せる。
def index
  @item_id = session[:item_id].values.first 
end

def show
  @item_id = session[:item_id].values.first
end

controllerで作成したインスタンス変数(@item_id)をviewで取り出し

app/views/credit_cards/index.html.haml
= link_to "もどる", "/items/#{@item_id}/purchase", class:'return'
app/views/credit_cards/show.html.haml
%button.enter__permit__box.btn(onclick="location.href='/items/#{@item_id}/purchase'")選択した支払い方法を使う
= link_to "もどる", "/items/#{@item_id}/purchase", class:'return'

これで items#purchase ⇄ credit_cards#show,index において item_idのやりとりが出来るようになった。
最後に商品購入をした際に、sessionの削除をする

app/controllers/items_controller.rb
def pay
  session[:item_id] = nil
end

今回はsessionを使ってパラメータのやりとりを行なったがURLを使用してパラメータをやりとりできるみたいなので、今度試してみたい。

なお、sessionを使用する際は、それなりにリスクを伴うので、使用する際は下記のリンク先も合わせて読んでおきたい。

合わせて読みたい記事(セッションを取り扱う際の注意点)

Rails セキュリティガイド

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

【Rails】Viewの共有

Viewの共有

editとnewなど、同じ入力内容を伴うviewをまとめて設定する。
今回用意するのは以下の3ファイル、editとnewは共通する入力フォームを有するものとする。
_form.html.erb
edit.html.erb
new.html.erb

共有フォームにてform_for(@profile)と記述するれば、ルーティングも自動で行う。
また、submitボタンの表記も自動化されており、日本語化していれば登録する更新すると表記される。

_form.html.erb

共有するフォームを記述する。

_form.html.erb
<%= form_for(@profile) do |f| %>

<div class="field">
  <%= f.label :img %><br />
  <%= f.file_field :img %>
  <%# 以下で赤文字でエラー内容を表示 %>
  <% if @profile.errors.include?(:img) %>
  <p style="color: red;"><%= @profile.errors.full_messages_for(:img).first %>
    <% end %>

    <div class="field">
      <%= f.label :name %><br />
      <%= f.text_field :name, autofocus: true, autocomplete: "name" %>
      <%# 以下で赤文字でエラー内容を表示 %>
      <% if @profile.errors.include?(:name) %>
      <p style="color: red;"><%= @profile.errors.full_messages_for(:name).first %>
        <% end %>

    </div>
    <div class="field">
      <%= f.label :age %><br />
      <%= f.number_field :age, autofocus: true, autocomplete: "age" %>
      <%# 以下で赤文字でエラー内容を表示 %>
      <% if @profile.errors.include?(:age) %>
      <p style="color: red;"><%= @profile.errors.full_messages_for(:age).first %>
        <% end %>
    </div>

    <div class="field">
      <%= f.label :男性 %><%= f.radio_button :sex, :男性 %>
      <%= f.label :女性 %><%= f.radio_button :sex, :女性 %>
      <%# 以下で赤文字でエラー内容を表示 %>
      <% if @profile.errors.include?(:sex) %>
      <p style="color: red;"><%= @profile.errors.full_messages_for(:sex).first %>
        <% end %>
    </div>

    <div class="field">
      <%= f.label :description %><br />
      <%= f.text_area :description, autofocus: true, autocomplete: "description" %>
      <%# 以下で赤文字でエラー内容を表示 %>
      <% if @profile.errors.include?(:description) %>
      <p style="color: red;"><%= @profile.errors.full_messages_for(:description).first %>
        <% end %>
    </div>

    <div class="field">
      <%= f.label :qualify %><br />
      <%= f.text_area :qualify, autofocus: true, autocomplete: "qualify" %>
      <%# 以下で赤文字でエラー内容を表示 %>
      <% if @profile.errors.include?(:qualify) %>
      <p style="color: red;"><%= @profile.errors.full_messages_for(:qualify).first %>
        <% end %>
    </div>

    <div class="field">
      <%= f.label :impression %><br />
      <%= f.text_area :impression, autofocus: true, autocomplete: "impression" %>
      <%# 以下で赤文字でエラー内容を表示 %>
      <% if @profile.errors.include?(:impression) %>
      <p style="color: red;"><%= @profile.errors.full_messages_for(:impression).first %>
        <% end %>
    </div>

    <div class="actions">
      <%= f.submit %>
    </div>
    <% end %>

edit.html.erb

renderで共有するフォームを呼び出し、profile: @profileでコントローラーから変数を受け取る。

edit.html.erb
<h1>プロフィール編集</h1>

<%= render 'form', profile: @profile %>

new.html.erb

renderで共有するフォームを呼び出す。

new.html.erb
<h1>プロフィール登録</h1>
<%= render 'form' %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Rails 教わった事 その1

この記事の目的

プログラミングスクールで私が約3ヶ月で教わった事を転職活動に生かすためにまとめました。
私による私のためだけの記事です。

この講義で印象に残った一言は、
Rails書けると出来るにはレベルに隔たりがある。

コードレビュー


メンターさんにとってすごい人だそうです!
Railsのコードレビューをするときに確認する観点をまとめてみた

コントローラーについて

スコープ

コントローラーの中で、モデルから直接メソッドを呼び出すことはセキュリティーホールに繋がりやすい。基本的に全体対象はイレギュラーであり、ほぼないものとする。↓

example_controller.rb
def new
  - @post = Post.new  #間違ったIDが入る可能性がある
  + @post = current_user.posts.build  #current_userの範囲(スコープ)
end 

Before_Action

コントローラー内での共通部分はprivateメソッドにして、before_actionでDRYに保つ↓

example_controller.rb
before_action :set_post

#省略
private

def set_post
  @post = current_user.posts.find(params[:id])
end

Save,Destroyメソッド

サーバートラブルなどネット環境の要因以外で、ほぼ失敗しないsave、destroyメソッドには!をつける↓

example_controller.rb
def create
  @post = current_user.posts.build(post_params)
  if @post.save! 
    flash[:notice] = "投稿しました"
    redirect_to posts_path
  else
    render :new
  end
end

定数を使う

全体の人に分かりやすくするよう定数にする。モデルで定数を定義し、コントローラーで呼び出す。↓

example_controller.rb
#ex_model.rb
PER_COMMENT = 5

#example_controller.rb
@comments = @post.comments.page(params[:page]).per(Ex_model::PER_COMMENT).order(created_at: :desc)
#モデル名::定数名

ルーティングについて

URLの意味を通す

resourcesをネストすると、URLの意味が分かりやすくなる。↓

routes.rb
#Post(投稿)が親、Comment(コメント)とLike(いいね)が子。
  resources :posts do
    resources :comments, only: [:create, :destroy]
    resources :likes, only: [:show,:create, :destroy]
  end

しかも、URLから2つ、IDを持ってくる事ができる!↓

post_comments POST     /posts/:post_id/comments(.:format)      comments#create
post_comment  DELETE   /posts/:post_id/comments/:id(.:format)  comments#destroy
post_likes    POST     /posts/:post_id/likes(.:format)         likes#create
post_like     GET      /posts/:post_id/likes/:id(.:format)     likes#show
              DELETE   /posts/:post_id/likes/:id(.:format)     likes#destroy

講義の感想

私よりも若い先生でしたが、とても立派な人です。
ZOOMで録画したものを、見返していますが当時よりはっきりと内容や意味を理解する事が出来ています。
その2へ続きます。

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

Rspecのファイルアップロードテスト時にcommitteeでCommittee::InvalidRequestが発生する事象への対処方法

発生しているエラー

     Committee::InvalidRequest:
       #/paths/~1contracts/post/requestBody/content/multipart~1form-data/schema/properties/original_file expected string, but received ActionDispatch::Http::UploadedFile: #<ActionDispatch::Http::UploadedFile:0x00007fa77e1f36a0>

何がしたいのか

multipart/form-data 形式でファイルアップロードを受け取るようなAPIを定義している。
APIはOpenAPI3.0の仕様に基づいて設計していて、例えば以下のようになる。

requestBody:
  original_file:
    image/png:
      schema:
        type: string
        format: binary

Swaggerのドキュメントにもある。

このAPIに対して committee gem を使ってRspecを書きたい。

テストの内容

some_controller_spec.rb
# formのパラメータ
let(:file_upload_form) {
  {
    article_id: 1_000,
    name: 'This is good article',
    # set real existing file
    original_file: fixture_file_upload(
      Rails.root.join('sample_files/sample.docx'),
      'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
    )
  }
}

Requestとして使うパラメータは上記のように定義していて、アップロードするファイルはテスト用に用意したDocxファイル。
これを以下のようにPOSTしてAPIのフォーマットを assert_schema_conform メソッドで検証している。

some_controller_spec.rb
it 'uploads file' do
  post api_v1_images_path,
         headers: authenticated_header(user),
         params: file_upload_form

  expect(response).to have_http_status(:success)
  assert_schema_conform
end

エラーの原因

APIのRequestの仕様が type: string なのに、テストでは ActionDispatch のオブジェクトが設定されているので、期待しているのと違う、っていうエラーが発生している。
しかし、ファイルアップロードのテストなのでStringでは内部の処理がうごかないので、変えることもできない。これは困った。

という状況

エラーの発生箇所

エラーは以下の OpenAPIParser の処理で発生していう。
lib/openapi_parser/schema_validators/string_validator.rb#L11

lib/openapi_parser/schema_validators/string_validator.rb
    def coerce_and_validate(value, schema, **_keyword_args)
      return OpenAPIParser::ValidateError.build_error_result(value, schema) unless value.kind_of?(String)

      # ... 以下省略 ...

    end

単純に valueString クラスかどうかをみていて、Stringじゃない場合はErrorになっている。今回はここが ActionDispatch のオブジェクトなので value.kind_of?(String) が偽となりエラーが発生してしまう。

type: stringであってもformat: binaryの場合はStringクラス以外も許す

ファイルのように、format: binayの場合はエラーをRaiseしないように条件を変えてやれば良い。そのためのパッチを作成する。
バッチとして該当のメソッド coerce_and_validate(value, schema, **_keyword_args) を定義した任意のModuleを作成する。

module StringValidatorPatch
  def coerce_and_validate(value, schema, **keyword_args)
    # この処理を変更
    # https://github.com/ota42y/openapi_parser/blob/61874f0190a86c09bdfb78de5f51cfb6ae16068b/lib/openapi_parser/schema_validators/string_validator.rb#L11
    if !value.is_a?(String) && schema.format != 'binary'
      return OpenAPIParser::ValidateError.build_error_result(value, schema)
    end
    # --- ここまで

    value, err = check_enum_include(value, schema)
    return [nil, err] if err

    value, err = pattern_validate(value, schema)
    return [nil, err] if err

    unless @datetime_coerce_class.nil?
      value, err = coerce_date_time(value, schema)
      return [nil, err] if err
    end

    value, err = validate_max_min_length(value, schema)
    return [nil, err] if err

    value, err = validate_email_format(value, schema)
    return [nil, err] if err

    value, err = validate_uuid_format(value, schema)
    return [nil, err] if err

    [value, nil]
  end
end

このように、変更したいメソッドだけを再定義したModuleを定義する。このModuleをパッチを当てたいクラス、今回はOpenAPIParser::SchemaValidator::StringValidatorクラスを再オープンし、Module#prependを使ってメソッドを上書きする。
Module#prependのリファレンス

class OpenAPIParser::SchemaValidator::StringValidator
  prepend StringValidatorPatch
end

これで Committee::InvalidRequest エラーが発生しなくなる

影響を最低限に抑える

パッチを当てることでCommittee::InvalidRequestエラーを回避することはできるが、グローバルなスコープでパッチを当ててしまうと全体に影響が出てしまう。
この変更はファイルアップロードを伴うテストの時だけ有効にしたい。
そのためRspecの必要なコンテキストでだけ反映されるように context 'some context' do ... end の中で定義することを考える。

some_controller_spec.rb
RSpec.describe SomeController, type: :request do
  context 'some context' do

    # context内でパッチを当てる
    module StringValidatorPatch
      def coerce_and_validate(value, schema, **keyword_args)
        if !value.is_a?(String) && schema.format != 'binary'
          return OpenAPIParser::ValidateError.build_error_result(value, schema)
        end
        # (以下略)
      end
    end

    class OpenAPIParser::SchemaValidator::StringValidator
      prepend StringValidatorPatch
    end
    # ここまでパッチ

    it 'uploads file' do
      post api_v1_images_path,
             headers: authenticated_header(user),
             params: file_upload_form

      expect(response).to have_http_status(:success)
      assert_schema_conform
    end
  end
end

しかし、ブロックのスコープは定数を分離したり名前空間を設定したりしないので、ブロック内でクラス定義などの処理は避けたい
Rubocopの Lint/ConstantDefinitionInBlockにもひっかかってしまう。
Rspecで同様の定数定義を行う場合は、stub_const() を使って定義する。

stub_const() を使って必要な箇所にパッチを適用する

クラスの再オープンをclassキーワードを使わずに行うには Class.newを使う。
ブロックを渡すことで、クラス定義も行える

Foo = Class.new {|c|
  def hello; 'hello'; end
}

puts Foo.new.hello # => 'hello'

これを使って、パッチを当てたクラスを定義する。パッチ用のStringValidatorPatchmoduleは spec/supportディレクトリに string_validator_patch.rb として保存し、読み込まれるようにしておく。

patched = Class.new(OpenAPIParser::SchemaValidator::StringValidator) do |klass|
  klass.prepend StringValidatorPatch
end

stub_const('OpenAPIParser::SchemaValidator::StringValidator', patched)

Class.new() の引数にクラスを渡すと親クラスとして扱われるので、上記の場合は patchedOpenAPIParser::SchemaValidator::StringValidator の子クラスとなる。
それを stub_const() を使って定数定義をしてやれば、パッチを当てたクラスを利用することができる。

この処理を必要に応じて beforelet で実装する。

まとめ

  • Committee::InvalidRequest エラーを解消するには、Validatorクラスにパッチを当てる
  • パッチを当てるには当該メソッドだけを実装したModuleを定義し、prependを使って反映する
  • RSpecではClass.new()stub_const() を使って局所的にパッチをあてる
spec/support/string_validator_patch.rb
module StringValidatorPatch
  def coerce_and_validate(value, schema, **keyword_args)
    # この処理を変更
    # https://github.com/ota42y/openapi_parser/blob/61874f0190a86c09bdfb78de5f51cfb6ae16068b/lib/openapi_parser/schema_validators/string_validator.rb#L11
    if !value.is_a?(String) && schema.format != 'binary'
      return OpenAPIParser::ValidateError.build_error_result(value, schema)
    end
    # --- ここまで

    value, err = check_enum_include(value, schema)
    return [nil, err] if err

    value, err = pattern_validate(value, schema)
    return [nil, err] if err

    unless @datetime_coerce_class.nil?
      value, err = coerce_date_time(value, schema)
      return [nil, err] if err
    end

    value, err = validate_max_min_length(value, schema)
    return [nil, err] if err

    value, err = validate_email_format(value, schema)
    return [nil, err] if err

    value, err = validate_uuid_format(value, schema)
    return [nil, err] if err

    [value, nil]
  end
end
some_controller_spec.rb
RSpec.describe SomeController, type: :request do
  context 'some context' do
    let(:file_upload_form) {
      {
        article_id: 1_000,
        name: 'This is good article',
        original_file: fixture_file_upload(
          Rails.root.join('sample_files/sample.docx'),
          'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
        )
      }
    }

    before do
      # context内でパッチを当てる
      patched = Class.new(OpenAPIParser::SchemaValidator::StringValidator) do |klass|
        klass.prepend StringValidatorPatch
      end
      stub_const('OpenAPIParser::SchemaValidator::StringValidator', patched)
    end

    it 'uploads file' do
      post api_v1_images_path,
           headers: authenticated_header(user),
           params: file_upload_form

      expect(response).to have_http_status(:success)
      assert_schema_conform
    end
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsでformをdisableして送信すると値が送信されてない場合の解消法

jsで一部formの値をdisableにしていたところ、値が送られていおらずその対処を行いました。

調べてみたところdisable 属性はPOSTされないらしく、これを回避してPOSTするためにはreadonly 属性が利用できるとこのことで追加してみました

<input placeholder="名" type="text" name="company" readonly="readonly">

参考記事

フォームでdisabled属性を指定すると送信されない

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

[Rails] 最小限の労力でrubocopの恩恵を受ける

はじめに

「railsプロジェクトやるときには、rubocop入れましょう」と呪文のように刷り込まれてきましたが、実は、rubocopで何ができるのか、どんなメリットが有るのかきちんと理解していなかったのでまとめました。

この手の「いろいろできるツール」の使う際の大事な心がけとしては、提供される機能をくまなく把握して全部使いこなすことではなく、導入の目的を満たす8割ぐらいのことを、最小限の労力でできるようにすることが大事なんじゃないかとつくづく思います。

この記事を読んで、rubocopを導入することだけでなく、労力を取られずに(=本来書くべきロジックやテストに集中できるように)コードの品質を高めるという成果が得られてもらえれば、何よりです。

rubocopとは?

一言でいうと、ruby(.rbファイル)のコードを検査して、定められた規約に違反している箇所を検出してくれるツールです。

「コードが長すぎる」「インデンが適切でない」とかコードの可読性を高めるものだけでなく、「明確にすべきオプションがされていない」「DBとmodelで整合性があっていない」などバグにつながるような規約違反も検出してくれます。そして多くの場合、自動的に修正もしてくれます。

また、railsを使用している場合には、rubocop-railsを同時利用することで、rails特有のファイル(ex. マイグレーションファイル、設定ファイル)も検査してくれます。

メリット

rubocopを導入することで受けられるメリット(恩恵)は以下のものだと思います。

  • バグにつながるような規約違反だったり、コードの読みやすさを阻害するような規約違反を自動検出してくれる
  • チーム開発の場合は、チームで定めた同じ設定ファイルを使用することで、各自が書いたコードを同一ルールに基づいた一定の品質に保つことの助けになる
  • 単純な規約違反(余計な余白がある、インデントが適切ではない)を自動的に修正してくれる
  • 自動実行ツール(pre-commit 後述)と組み合わせることで、意識せずにcommitのタイミングで自動実行して、規約違反を検出してくれる。(そもそもの実行し忘れるということを防げる)
  • コードレビューを機械的に行ってくれるので、規約違反の内容を理解し修正することで、自分のコードの品質を高めることができる

rubocopの使い方

railsを使用している場合の導入〜使い方を記載してきます。

試した環境は以下です。

  • OS : macOS Catalina(10.15.7)
  • ruby : 2.6.6
  • rails : 6.0.3.3

また、私の場合まずは作るの優先でやったrailsアプリがあり、model,contorllerともに5,6個程度作成した状態で導入しました。

なお、私は導入に当たり以下のことを心がけました。なので、他の方が記載された導入の仕方や、設定内容とは異なる部分、相容れない部分があると思います。

  • いきなり100%を目指さない
  • ツールがやってくれることは、ツールに任せ、自分がやらなきゃいけないことに注力する
  • 継続的に使えるようにする

インストール&設定

Gemfileに以下の記載を行い、bundle install

group :development do
  gem 'rubocop', require: false
  gem 'rubocop-rails'
end

とりあえずチェックする

$ rubocop
Inspecting 57 files
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Offenses:
57 files inspected, 292 offenses detected, 260 offenses auto-correctable

292個って、、めちゃくちゃ出ましたね・・・ しかもスクロースしてもすべては表示しきれずorz

これを、一個一個確認して、修正。なんてことをやってくと。もうrubocopなんてしない!と投げ出してしまうので、継続的に使える有用なツールにしましょう。

というわけで、次の「設定ファイルの作成」と「自動修正」を行っています。

設定ファイルの作成

以下のコマンドを実行すると、設定ファイル(.rubocop.yml)を作成してくれ、かつ検出された規約違反を読み飛ばすための設定を(.rubocop_todo.yml)に記載してくれます。

$ rubocop --auto-gen-config

適用する(しない)規約・範囲を設定ファイルに書いてあげます。

私は、とりあえず「チェック対象を自分で書いたコードに限定する」という考えで、以下の設定を入れてみました。

  • コマンドで自動生成されたファイルや、初期ファイルはチェック対象外にする
AllCops:
  TargetRubyVersion: 2.6
  NewCops: enable    # ← 新しい規約が登録された場合に、適用するかどうかの判定
  Exclude:
    - 'bin/**'
    - 'node_modules/**/*'
    - 'config/**/*'
    - 'config.ru'
    - 'db/schema.rb'
    - 'db/seeds.rb'
    - 'Gemfile'
  • 自分のコード記載方法にそぐわない、エラー検出が必要ない規約を無効or変更
# 日本語でのコメントを許可
Style/AsciiComments:
  Enabled: false

# クラスのコメント必須を無視
Style/Documentation:
  Enabled: false

# 「frozen_string_literal: true」を追加しない
Style/FrozenStringLiteralComment:
  Enabled: false

# メソッドの行数が 10 行までは厳しすぎるので,20行までに変更
Metrics/MethodLength:
  Max: 20

# private/protected は一段深くインデントする
Style/IndentationConsistency:
  EnforcedStyle: indented_internal_methods

これらの設定ファイルを記載したうえで、規約違反を退避したファイル(.rubocop_todo.yml)の中身をコメントアウトして、再度rubocopを実行します。私の場合50個くらいまで減りました。

ここまで来てもまだすべてを一つづつ直す気にはなれないので、次の自動修正を実行しします。

自動修正

冒頭にも記載したとおり、簡単な(かつ対処法が明確な)規約違反はrubocop -aコマンドで自動的に修正してくれます。

※ここは、一つ一つ規約違反の内容確認した上で、自動修正するべきという意見もありますが、私は「自動修正してくれるものは任せよう」と割り切って、規約違反はサーと見て自動修正しました。

実行すると、こんな感じで、自動修正された規約違反は[Corrected] が付与され、最後に、規約違反の総件数に対して、何件自動修正されたか表示されます。

$ rubocop -a
.rubocop.yml: Style/IndentationConsistency has the wrong namespace - should be Layout
Inspecting 29 files
....................CC.CCCCCC

Offenses:

db/migrate/20200928124523_devise_create_users.rb:6:59: C: [Corrected] Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.
      t.string :nickname,           null: false, default: ""
                                                          ^^

~ 途中略 

29 files inspected, 24 offenses detected, 22 offenses corrected

手動修正

そして、自動修正しても残ってしまった規約違反が、自分で対応しなきゃいけない(=本来やりたかった、時間をかけて、内容理解し、あるべきコードに修正する作業)ものです。

私の場合は、以下の2つが残りました。こちらはバグに繋がる可能性のある規約違反だと思うので、エラーの内容を確認して(ex. Rails/HasManyOrHasOneDependent というキーワードでググれば、公式サイトの内容or丁寧解説記事にありつけます)、修正方法を検討して修正し、再度rubocopを実行して、無事に規約違反なしとなりました。

# has_manyアソシエーションにたいして、dependentオプション(親レコード削除時に、同時に消す? 残す? エラーにする? 警告出す?)が未設定
app/models/category.rb:3:3: C: Rails/HasManyOrHasOneDependent: Specify a :dependent option.
  has_many :estimate_details
  ^^^^^^^^

# modelでuniqueバリデーションを定義しているのに、DB定義にはunique定義がされていない
app/models/category.rb:6:3: C: Rails/UniqueValidationWithoutIndex: Uniqueness validation should be with a unique index.
  validates :user_id, uniqueness: { scope: :category_name }

自動実行

便利なツールですが、そもそも実行することを忘れないために、何かを契機に自動的に実行するようにしましょう。一番良いタイミングがcommitのタイミングだと思うので、pre-commitというgemを導入して、git commitコマンド発行したタイミングで自動実行するようにします。

  • pre-commitのインストール

gem pre-commitをGemfileに設定して、bundle install したあとに、以下のコマンドでpre-commitのファイルを生成します。

$ pre-commit install
Installed /Users/hiro/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/pre-commit-0.39.0/templates/hooks/automatic to .git/hooks/pre-commit
  • pre-commitの設定

commit時にrubocopを自動実行するために、以下コマンドで設定を行います。

# 設定前の状態を確認 
$ pre-commit list    
Available providers: default(0) git(10) git_old(11) yaml(20) env(30)
Available checks   : before_all ci coffeelint common console_log csslint debugger gemfile_path go go_build go_fmt jshint jslint json local merge_conflict migration nb_space pry rails rspec_focus rubocop ruby ruby_symbol_hashrockets scss_lint tabs whitespace yaml
Default   checks   : common rails
Enabled   checks   : common rails
Evaluated checks   : tabs nb_space whitespace merge_conflict debugger pry local jshint console_log migration
Default   warnings : 
Enabled   warnings : 
Evaluated warnings :

# git commitのタイミングで、rubocopを実行するように設定
$ git config pre-commit.checks rubocop

# 設定後の状態を確認
$ pre-commit list    
Available providers: default(0) git(10) git_old(11) yaml(20) env(30)
Available checks   : before_all ci coffeelint common console_log csslint debugger gemfile_path go go_build go_fmt jshint jslint json local merge_conflict migration nb_space pry rails rspec_focus rubocop ruby ruby_symbol_hashrockets scss_lint tabs whitespace yaml
Default   checks   : rubocop   # ← rubocopが設定された
Enabled   checks   : rubocop   # ← rubocopが設定された
Evaluated checks   : rubocop   # ← rubocopが設定された
Default   warnings : 
Enabled   warnings : 
Evaluated warnings :

また、bundle経由で、pre-commtiを使用する場合には.git配下の設定ファイル(.git/hooks/pre-commit)を以下のように修正する必要があります。

#!/usr/bin/env sh

 中略 

PATH=$PATH:/usr/local/bin:/usr/local/sbin

cmd=`git config pre-commit.ruby 2>/dev/null`
if   test -n "${cmd}"
then true
elif which rvm   >/dev/null 2>/dev/null
then cmd="rvm default do ruby"
elif which rbenv >/dev/null 2>/dev/null
then cmd="rbenv exec ruby"  #← 修正前
then cmd="rbenv exec bundle exec ruby"  #← 修正後
else cmd="ruby"
fi

 中略 

設定完了後に、git commitコマンドを実行して、rubocopが自動実行されるか確認します。

(規約違反が出るように事前に行末余白を仕込んでおきました)

$ git commit
pre-commit: Stopping commit because of errors.
Inspecting 1 file
C

Offenses:

app/controllers/home_controller.rb:4:1: C: Layout/TrailingWhitespace: Trailing whitespace detected.

1 file inspected, 1 offense detected, 1 offense auto-correctable
.rubocop.yml: Style/IndentationConsistency has the wrong namespace - should be Layout

pre-commit: You can bypass this check using `git commit -n`

commit時にrubocopが自動実行されることが確認できました。違反が検出されると、commit処理は中断されます。

検出後の流れとしては、「違反内容確認」→(修正すべき場合は)「マニュアル or 自動修正(rubocop -a)」→ 「ステージング環境へ登録」→「commit」の流れになるかと思います。

おわりに

今後、commitのために自動的にrubocopが走り、チェックが行われる設定ができ継続的に使えるようになりました。
コードを書いて、新たな規約違反が出たときに、設定ファイルを見直したり、内容を確認して書き方を改めることができていければ、よりよいツールを育てていけると思いますし、自分のコード品質を高めることにもつながるのではないかと、考えております。

参考にさせていただいた記事

使い方に関して、完全に無知でしたのでとても参考になりました。
記事の著者の方にはこの場を借りて、感謝のお礼をさせていただきます。

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

【Ruby on Rails】MySQL構築からデータベース変更まで

はじめに

様々な記事を参考にしながらなんとか導入できたので、
備忘録として残します。
もしおかしな点や、こうした方がいいなどありましたら
ご教授頂けますと幸いです。

開発環境

ruby 2.5.7
Rails 5.2.4.3
Vagrant 2.2.4
VirtualBox 6.0.14
OS: macOS Catalina
centos 7

流れ

1 vagrant上にMySQLを構築
2 既存アプリをSqliteからMySQLに変更
3 新規アプリをMySQLに設定
※基本的にはvagrant上で行うため、ssh接続しておいてください。

vagrant上にMySQLを構築

CentOS確認

まずは現在のCentOSを確認します。
確認方法はvagrantファイルにあるVagrantfileを確認します。

Vagrantfile
Vagrant.configure("2") do |config|
    GUEST_RUBY_VERSION = '2.5.7'
    config.vm.box = "centos/7"

...

今回はCentOSが7である前提で話を進めます。
CentOSとは仮想環境構築に使用する代表的なLinux系OSです。

MySQLのインストール(CentOS7用)

Vagrant+Rails6+MySQL 開発環境構築
こちらの記事を参考にまずはvagrant上にMySQLを構築します。

ターミナル
$ vagrant ssh
$ sudo yum -y install http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm
$ sudo yum -y install mysql-community-server
$ mysqld --version

バーションが表示されればOKです。

自動起動設定後、MySQL起動

vagrant起動時に自動で起動するように設定します。
2行目はmysqld.service enabledになっていればOKです。

ターミナル
$ sudo systemctl enable mysqld.service
$ sudo systemctl list-unit-files -t service | grep mysqld
$ sudo systemctl start mysqld.service

MySQL初期設定(任意)

https://style.potepan.com/articles/19020.html
こちらの記事がわかりやすかったので、
$ mysql_secure_installation を実行後、
この記事のMySQLの初期設定を実施しよう!から設定してください。

# rootユーザーにパスワードを設定(今回はrootパスワードを設定)
$ /usr/bin/mysqladmin -u root password 'root'

# セキュリティー関連の初期設定(ここでパスワードを聞かれると'root'とする)
$ mysql_secure_installation

設定後、下記を実行しパスワードを入力後、
mysql>
この表示になればOKです。

ターミナル
$ mysql -u root -p

既存アプリをSqliteからMySQLに変更

Railsでmysql2をインストールするときにハマったところ
[初学者]既存アプリのDBをMySQLに変更する方法
上記記事を参考に導入していきます。

railsアプリの作成、データベース確認

試しにscaffoldでpostテーブルを作成します。

ターミナル
$ rails new sam
$ cd sam
$ rails g scaffold post name:string

congig/database.ymlがこのような表記になっているかと思います。
初期設定ではsqlite3のデータベースをしようしています。

congig/database.yml
default: &default
  adapter: sqlite3
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development:
  <<: *default
  database: db/development.sqlite3

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: db/test.sqlite3

production:
  <<: *default
  database: db/production.sqlite3

gemの導入

Gemfile
# Use sqlite3 as the database for Active Record
gem 'sqlite3'

gem 'mysql2'
ターミナル
$ bundle install

エラーが出る場合は下記を実行するか、
Gemfileのgem 'mysql2'のバージョンを
gem 'mysql2', '~> 0.4.4'
に変更してみてください。

ターミナル
$ sudo yum install -y mysql-devel

データベース設定をMySQLに変更

congig/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: root
  password: 初期設定をした場合はパスワードを記述
  host: localhost

development:
  <<: *default
  database: sam_development # samはアプリ名です。

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: sam_test # samはアプリ名です。

production:
  <<: *default
  database: sample_production
  username: sample_app
  password: <%= ENV['SAMPLE_DATABASE_PASSWORD'] %>

下記でデータベースを作成。

ターミナル
$ bundle exec rake db:create
$ rails db:migrate
ターミナル
$ mysql -u root -p
$ show tables from sam_development;
+---------------------------+
| Tables_in_sam_development |
+---------------------------+
| ar_internal_metadata      |
| posts                     |
| schema_migrations         |
+---------------------------+
3 rows in set (0.00 sec)

このようになっていたら設定完了です。

新規アプリをMySQLに設定

こちらは既存アプリの変更より簡単に出来ます。

ターミナル
$ rails new sample -d mysql
$ cd sample
$ rails g scaffold post name:string
congig/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: 初期設定をした場合はパスワードを記述
  socket: /var/lib/mysql/mysql.sock

development:
  <<: *default
  database: sample_development

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: sample_test

# As with config/secrets.yml, you never want to store sensitive information,
# like your database password, in your source code. If your source code is
# ever seen by anyone, they now have access to your database.
#
# Instead, provide the password as a unix environment variable when you boot
# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
# for a full rundown on how to provide these environment variables in a
# production deployment.
#
# On Heroku and other platform providers, you may have a full connection URL
# available as an environment variable. For example:
#
#   DATABASE_URL="mysql2://myuser:mypass@localhost/somedatabase"
#
# You can use this database configuration with:
#
#   production:
#     url: <%= ENV['DATABASE_URL'] %>
#
production:
  <<: *default
  database: sample_production
  username: sample
  password: <%= ENV['SAMPLE_DATABASE_PASSWORD'] %>
Gemfile
# Use mysql as the database for Active Record
gem 'mysql2', '>= 0.4.4', '< 0.6.0'
ターミナル
$ bundle exec rake db:create
$ rails db:migrate

もしAccess deniedのエラーが出た場合は、
下記のように一度ログイン後、再度上記を実行してください。

ターミナル
$ mysql -u root -p
exit

まとめ

初期設定を行うことによってAccess deniedで弾かれるこtがありますが、
セキュリティー上仕方がないことかもしれません。
間違っている記述や方法がございましたらご教授頂けますと幸いです。

またtwitterではQiitaにはアップしていない技術や考え方もアップしていますので、
よければフォローして頂けると嬉しいです。
詳しくはこちら https://twitter.com/japwork

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

【Rails】バリデーションの設定と日本語化

バリデーションの設定

modelファイルに記述します。
presence: true によって入力必須となります。
そのほかにもいろいろな設定が可能です。詳細は公式ドキュメント等を参照願います。

profile.rb
class Profile < ApplicationRecord
  belongs_to :user
  validates :name, presence: true
  validates :age, presence: true
  validates :sex, presence: true
  validates :description, presence: true
  validates :qualify, presence: true
  validates :impression, presence: true
end

エラーメッセージの表示

いろいろな方法がありますが、今回はヘルパーif @変数名.errors.include?を使用して以下のようにしました。
nameについてのみ記述しておりますが、nameを別のカラム名に変更することでメッセージの表示が可能です。

new.html.erb
<div class="field">
  <%= f.label :name %><br />
  <%= f.text_field :name, autofocus: true, autocomplete: "name" %>

<%# 以下で赤文字でエラー内容を表示 %>
  <% if @profile.errors.include?(:name) %>
  <p style="color: red;"><%= @profile.errors.full_messages_for(:name).first %>
    <% end %>

表示すると以下のような感じになります。
※現段階では日本語化していないので、表示される赤文字は英語のはずです。日本語化については後段で触れます。

Screen Shot 2020-10-12 at 13.26.32.png

エラーメッセージの日本語化 :1(メッセージ)

今回はgemを使いますが、日本語の言語ファイルをGit経由で入手可能です。
興味がある方はrails-i18nで検索してください。
また、この作業ではカラム名が英語のままです。カラム名日本語化は次の段落で行います。

gem 'rails-i18n'
bundel install

エラーメッセージの日本語化 :2(カラム名)

config/locales/modelsディレクトリにja.ymlファイルを作成します。
インデントがずれると正しく動作しないので、画像も参考にしてください(点の数=インデント幅)。

ja.yml
ja:
  activerecord:
    models:
      profile: プロフィール #日本語化したいカラムがあるmodel名
    attributes:
      profile:
        name: 名前
        age: 年齢
        sex: 性別
        description: 概要
        qualify: 資格
        impression: 意気込み

Screen Shot 2020-10-12 at 13.39.23.png

参考に、バリデーションの内容を再度掲載します。

profile.rb
class Profile < ApplicationRecord
  belongs_to :user
  validates :name, presence: true
  validates :age, presence: true
  validates :sex, presence: true
  validates :description, presence: true
  validates :qualify, presence: true
  validates :impression, presence: true
end

エラーメッセージの日本語化 :3(読み込み)

最後に、当該ymlファイルを読み込ませる必要があります。
config/application.rbに以下の通り一文を追記してください。
これにより、ディレクトリ内のすべてのymlファイルを読み込むことができます。

application.rb
module Association03
  class Application < Rails::Application
    config.load_defaults 5.1
    config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.yml').to_s] #追記した一文

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

[Rails] Herokuデプロイの流れ

備忘録

Herokuを使ったデプロイよく忘れてしまうので記録用
初学者のため間違っていたらご指摘お願いします。
尚本環境ではRails、MySQLを使っています。
プログラミング初心者の方に役立てばと思います!

手順1 Heroku CLIをインストール

こちら最初のデプロイのみです。
2回目以降の方は手順2からご覧下さい

ターミナル

使用するディレクトリ内で以下のコマンドを入力
% brew tap heroku/brew && brew install heroku

このコマンドでherokuコマンドが使用できるようになり、ターミナルからHerokuにログインできるようになります。

手順2 Herokuにログインしましょう

ターミナルで以下を入力
% heroku login --interactive
  => Enter your Heroku credentials.
# メールアドレスを入力し、エンターキーを押す
  => Email:
# パスワードを入力して、エンターキーを押す
  => Password:

これでHerokuにログインできました!

手順3 rails_12factorを導入

Railsアプリケーションを本番環境などのサーバ上で動かすためのアセットがまとまったGem

# Gemfileに追加
group :production do
  gem 'rails_12factor'
end
gemインストール
% bundle install
編集したのでコミット
% git add .
% git commit -m "gem rails_12factorの追加"

デプロイはリモートリポジトリ上のデータを使うので変更の都度コミットしてプッシュを行うことを忘れないで下さい。

手順4 Herokuにアプリを作成

heroku createコマンドでheroku上にアプリを作成します。
heroku create 作成したいアプリ名

ターミナルにて入力
例
% heroku create heroku-test01

手順5 HerokuでMySQLを使えるようにする

ClearDBアドオンを追加する事でHeroku内でMySQLを使用できるようになります。

ターミナルで以下コマンドを実行
heroku addons:add cleardb
ターミナルで以下入力
# ClearDBデータベースのURLを変数heroku_cleardb
% heroku_cleardb=`heroku config:get CLEARDB_DATABASE_URL`

# データベースのURLを再設定
% heroku config:set DATABASE_URL=mysql2${heroku_cleardb:5}

ここまででMySQLを使えるようになりました。
手順4に進む前にcredentials.yml.encファイルとmaster.keyファイルについて軽く説明します。

credentials.yml.encファイルはrailsで外部に漏らしたくない情報を扱うファイルのこと。

master.keyファイルファイルはcredentials.yml.encの暗号文を複号するためのファイルのことです。
master.keyは重要なファイルのためデフォルトでGitで扱われないようになっています。

手順5.5 credentials.yml.encをmaster.keyで複号

以下コマンドでcredentials.yml.encをmaster.keyによって復号し、中身を確認できた状態です

# ターミナルで以下コマンドを実行
% EDITOR="vi" bin/rails credentials:edit

手順6 Heroku上にmaster.keyを設置

先ほども説明しましたが、master.keyはGitで扱えないため環境変数を設定する必要があります。
heroku config:set 環境変数名="値"
これにより、Heroku上で環境変数を設定でき、master.keyを使えるようになります。

ターミナルで以下入力
heroku config:set RAILS_MASTER_KEY=`cat config/master.key`
以下コマンドで環境変数の設定を確認
% heroku config

手順7 アプリをプッシュ

git push heroku masterコマンドでheroku上のアプリにリモートリポジトリの内容をプッシュしています。

以下ターミナルで実行
% git push heroku master

ここまででHeroku上にアプリケーションを反映する事ができました!
ですがマイグレーション情報が反映されていません、、、

手順8 Herokuでマイグレーションを実行

heroku run rails db:migrateコマンドでHerokuのDB上にマイグレーション情報を反映させています。

ターミナルで以下コマンド実行
% heroku run rails db:migrate
# 以下コマンドでHeroku上にデプロイしたアプリの情報確認できます
% heroku apps:info

追記

今後追加機能などを本番環境にデプロイする際には、コミット→プッシュ→手順7で簡単にデプロイする事ができます。

まとめ

お疲れ様です!
ここまでで一通りのデプロイ作業が終了しました!
実際の現場ではHerokuを使う事はほとんど無いと思いますが、私を含め初学者にとっては簡単にデプロイできるツールなのでデプロイの入門としてとても便利ですね!
誰かの助けになれば幸いです。

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

かるく投稿してみる

初投稿なので軽く書き方を学んでいく

とりあえずhello!を書き出してみる

hello!

rubyで出力するためのコードを書く。
:で名前指定

hello.rb
return "hello!"

ヘッダー


#と___を使ってヘッダーを作る。

おわりに


基本的な書き方がわかったのでこれから少しずつ学んだことを投稿していく。
文章力まだまだ。

ご指摘、アドバイス等あったらおねがいします。

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

Ruby on Rails ログイン機能のバリデーション設定② メールの正規表現

前回の記事の続きになります。

name、emailカラムにバリデーションを設置した後、有効なメールアドレス(sample@sample.comなど)かどうかを判別するために正規表現を使おうと思いますが、Rubyのリファレンスを見ても膨大な情報に溺れてしまいそう。。。そこで今回はemailが有効かどうかを判別するのに必要な分だけを調べて使ってみることにしました。

結論からいいますと、今回使った正規表現はこちら。

/\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i

正規表現についてはRubyのリファレンスよりもRailsチュートリアルの方が分かりやすく説明されていました。下記はRailsチュートリアルよりの抜粋ですが、これを見ながらだと理解できそうです。

正規表現 意味
/\A[\w+-.]+@[a-z\d-.]+.[a-z]+\z/i (完全な正規表現)
/ 正規表現の開始を示す
\A 文字列の先頭
[\w+-.]+ 英数字、アンダースコア (_)、プラス (+)、ハイフン (-)、ドット (.) のいずれかを少なくとも1文字以上繰り返す
@ アットマーク
[a-z\d-.]+ 英小文字、数字、ハイフン、ドットのいずれかを少なくとも1文字以上繰り返す
. ドット
[a-z]+ 英小文字を少なくとも1文字以上繰り返す
\z 文字列の末尾
/ 正規表現の終わりを示す
i 大文字小文字を無視するオプション

さらにRubularを使えば正規表現を簡単にチェックできるようです。これは便利!

・・・で、この正規表現を前回の記事で作成したバリデーションに追加します。フォーマットを検証するためには。。。

validates :email, format: { with: /<regular expression>/ }

このような形でformatオプションを使用するとのことなので、"regular expression"の箇所に正規表現を追加。
app/models/user.rb

class User < ApplicationRecord
  validates :name,  presence: true, length: { maximum: 20 }
  validates :email, presence: true, length: { maximum: 300 }, format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i }
end

Rails consoleで確認してみると、sample@sample.comのような形でないので、エラーになっていました。

> user = User.create(name: "ruby", email: "ruby")
   (0.2ms)  BEGIN
   (0.3ms)  ROLLBACK
 => #<User id: nil, name: "ruby", email: "ruby", created_at: nil, updated_at: nil>
> user.errors.messages
=> {:email=>["is invalid"]}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails:メールの正規表現を攻略する!

前回の記事の続きになります。

name、emailカラムにバリデーションを設置した後、有効なメールアドレス(sample@sample.comなど)かどうかを判別するために正規表現を使おうと思いますが、Rubyのリファレンスを見ても膨大な情報に溺れてしまいそう。。。そこで今回はemailが有効かどうかを判別するのに必要な分だけを調べて使ってみることにしました。

結論からいいますと、今回使った正規表現はこちら。

/\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i

正規表現についてはRubyのリファレンスよりもRailsチュートリアルの方が分かりやすく説明されていました。下記はRailsチュートリアルよりの抜粋ですが、これを見ながらだと理解できそうです。

正規表現 意味
/\A[\w+-.]+@[a-z\d-.]+.[a-z]+\z/i (完全な正規表現)
/ 正規表現の開始を示す
\A 文字列の先頭
[\w+-.]+ 英数字、アンダースコア (_)、プラス (+)、ハイフン (-)、ドット (.) のいずれかを少なくとも1文字以上繰り返す
@ アットマーク
[a-z\d-.]+ 英小文字、数字、ハイフン、ドットのいずれかを少なくとも1文字以上繰り返す
. ドット
[a-z]+ 英小文字を少なくとも1文字以上繰り返す
\z 文字列の末尾
/ 正規表現の終わりを示す
i 大文字小文字を無視するオプション

さらにRubularを使えば正規表現を簡単にチェックできるようです。これは便利!

・・・で、この正規表現を前回の記事で作成したバリデーションに追加します。フォーマットを検証するためには。。。

validates :email, format: { with: /<regular expression>/ }

このような形でformatオプションを使用するとのことなので、"regular expression"の箇所に正規表現を追加。
app/models/user.rb

class User < ApplicationRecord
  validates :name,  presence: true, length: { maximum: 20 }
  validates :email, presence: true, length: { maximum: 300 }, format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i }
end

Rails consoleで確認してみると、sample@sample.comのような形でないので、エラーになっていました。

> user = User.create(name: "ruby", email: "ruby")
   (0.2ms)  BEGIN
   (0.3ms)  ROLLBACK
 => #<User id: nil, name: "ruby", email: "ruby", created_at: nil, updated_at: nil>
> user.errors.messages
=> {:email=>["is invalid"]}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

mysql2のbundle installエラー解消

  • macOS Catalina
  • zsh

rubyのプロジェクトの環境構築にて

1. mysqlないよ

% bundle install

怒られる

An error occurred while installing mysql2 (0.5.3), and Bundler
cannot continue.
Make sure that `gem install mysql2 -v '0.5.3' --source
'https://rubygems.org/'` succeeds before bundling.

2. mysql2こける(ライブラリないよ)

言われたとおりに

% gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'

怒られる

ld: library not found for -lssl

対処

https://qiita.com/fukuda_fu/items/463a39406ce713396403

/usr/local/opt/opensslにopenssl入ってるか確認。

わたしの場合は、homebrewでopenssl@1.1をインストール済で、.zshrcにはパス通してたんだけど、

.zshrc
export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"

どうやらmysql2が使いたいのはこちらの(最新の)パスのほうではなくて下記のパスのほうだそうなので、ローカルパスを通す。

% export LDFLAGS="-L/usr/local/opt/openssl/lib"
% export CPPFLAGS="-I/usr/local/opt/openssl/include"

ローカルオプションを設定

% bundle config --local build.mysql2 "--with-cppflags=-I/usr/local/opt/openssl/include"
% bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl/lib"

2. Pumaでこける

再度

bundle install

怒られる

An error occurred while installing puma (4.3.1), and Bundler cannot
continue.
Make sure that `gem install puma -v '4.3.1' --source 'https://rubygems.org/'`
succeeds before bundling.

言われたとおりに

% gem install puma -v '4.3.1' --source 'https://rubygems.org/'

怒られる

puma_http11.c:203:22: error: implicitly declaring library function 'isspace' with type 'int (int)' [-Werror,-Wimplicit-function-declaration]
  while (vlen > 0 && isspace(value[vlen - 1])) vlen--;
                     ^
puma_http11.c:203:22: note: include the header <ctype.h> or explicitly provide a declaration for 'isspace'
1 error generated.
make: *** [puma_http11.o] Error 1

make failed, exit code 2

対処

https://github.com/puma/puma/issues/2304

インストールオプションつける

% gem install puma:4.3.1 -- --with-cflags="-Wno-error=implicit-function-declaration"

成功

Successfully installed puma-4.3.1
Parsing documentation for puma-4.3.1
Installing ri documentation for puma-4.3.1
Done installing documentation for puma after 0 seconds
1 gem installed

ようやく

次こそ・・

% bundle install
Bundle complete! 24 Gemfile dependencies, 90 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

ホっ?
(最近環境構築しすぎて、このへんの解消に僧侶モードはいってきました)
https://qiita.com/mksm_wrk/items/69f6c2fc396dc0abf954

さあ仕事しよ。

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

【ざっくり解説】ActiveHashモデルからnameが取得できない原因と対処法

「ActiveHashのモデルからnameを取得したいんだけど、undefinedエラーが出て取得できない…」

そんな方向けに、ActiveHashのnameを多少強引に取得する方法と、そもそもnameが取得できない理由について解説していきます。

プログラミング初心者の方は参考にしていただけると幸いです。
(なぜエラーが出るかご存知の方にとっては、この記事は無益です。ごめんなさい。)

この記事の目的

・ActiveHashを使った正常な値の取得方法を復習する
・ActiveHashモデルからfindメソッドで値を取得する方法を学習する
・なぜundefindエラーが発生するのかを理解する

【前提】 ActiveHash 正常な値の取得

sushi.rb
class Sushi < ActiveHash::Base
  self.data = [
    { id: 1, name: '大トロ' },
    { id: 2, name: '中トロ' },
    { id: 3, name: 'いくら' },
    { id: 4, name: '穴子' },
    { id: 5, name: 'えんがわ' },
    { id: 6, name: '雲丹' },
    { id: 7, name: 'いか' }
  ] 
end
lunch.html.erb
<%= @shari.sushi.name %>
<!-- @shariのsushi_idが「4」 の場合、「穴子」が表示される-->

このような記述で、ActiveHashのモデルから、@shari.sushi_idに紐づくnameの値を取得することができます。(今回モデルの記述は割愛します。)

しかし、ある条件下では、ActiveHashのname要素を取得することができず

undefined method '〇〇' for …

のように、エラーとなってしまう場合があります。

そんな時に、無理やりActiveHashのモデルから値を取得する方法をお伝えします。

その①:findを使って取得・表示する

lunch.html.erb
<%= Sushi.find(@shari.neta_id).name %>

「エラーは起きたけど、せっかくActiveHashモデル書いたし使いたいな…」

という方にオススメなのは、findメソッドを使って無理やり取得する方法です。

①findメソッドの引数に取得したいidを渡し、Sushiモデル(ActiveHash)から@shari.neta_idを探します。
②取得したidのnameが欲しいため、末尾に.nameをつけてあげます。
③Sushiモデルから、@shari.neta_idに合わせたname要素が取得できます。

多少強引な方法ではありますが、この方法を使えば指定のname要素を取得することができます。ただし、コードが冗長になりやすいので、使うときは注意が必要です。

その②:form.select と Caseで取得・表示する

new.html.erb
<%= form.select :neta_id,[["アジ",1],["コハダ",2],["赤むつ",3],["アマダイ",4]] %>
lunch.html.erb
<% case @shari.neta_id %>
  <% when 1 then %>
     <p>アジ</p>
  <% when 2 then %>
     <p>コハダ</p>
  <% when 3 then %>
     <p>赤むつ</p>
  <% when 4 then %>
     <p>アマダイ</p>
<% end %>

「記述量は少ないし、ActiveHashじゃなくてもいいかな…」

という方は、プルダウン部分をform.selectに、表示部分をcase文で記述することで、擬似的にActiveHashのような表現をすることができます。

ただし、この方法は選択肢が増えると記述量が膨大になり、コードの可読性を下げる可能性があるので、選択肢が少ない場合のみ使用することをオススメします。

そもそもなぜ取得できないの?

結論、「ActiveHashモデルのモデル名とカラム名が合っていない」場合、name要素を正規の方法で取得することができません。

カラム名がsushi_id(integer型)であった場合、ActiveHashのモデル名もSushi.rbである必要があります。前述その①の場合、Sushi.rbからneta.nameのような形で値を取得しようとしても、取得できません。

ActiveHashモデルを作成してプルダウンやチェックボックスを作る際は、モデルの名前とカラムの名前を揃えるようにしましょう。

まとめ

①ActiveHashはモデル名とカラム名があっていないと、.nameでname要素を取得することができない。
②別名のモデルから値を取得する場合は、findを使うことでname要素を取得することができる。
③記述そのもの(選択肢)が少ない場合は、form.selectとwhenを使うことで表現する方法もある。

最後までご覧いただきありがとうございました。
引き続き、学習を頑張っていきましょう!

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

AWS Cloud9 でRuby on Rails の開発環境を構築する

はじめに

ふとRuby on Railsの勉強をはじめようと思い、環境構築をすることにしました。
せっかくなので、AWS Cloud9で環境構築をしようと思います。

やること

この記事では、以下のことを実施します。

  • AWS Cloud9環境構築
  • Ruby 環境構築
    • rvmを使用したruby version 2.5.1のインストールと切り替え
  • Rails 環境構築
    • gemを使用したrails version 5.2.1のインストール

AWS Cloud9環境構築

Cloud9は、クラウド環境でIDE(統合開発環境)を利用できるサービスです。

ブラウザ上で動くため、OSやその他環境が異なるPCでも同じ手順で開発環境を準備することができます。
特徴 - AWS Cloud9 | AWS

Cloud9環境作成

AWS マネジメントコンソールからCloud9の画面に遷移します。

Create enviroimentをクリックします。
20201011023747.png

環境設定

作成するCloud9環境の設定をしていきます。

NameにCloud9環境の名前をつけます。
自由に決定できるようですが、今回はruby-cloud9-envにします。

入力できたらNext stepをクリックします。

20201011023821.png

Cloud9環境に使用するEC2インスタンス(仮想マシン)の設定をします。
全てデフォルトで大丈夫です。

Next stepをクリックします。

20201011023907.png

設定確認

構築する環境の設定を確認できます。
確認したらCreate enviroimentをクリックします。

20201011023920.png

Cloud9のIDE画面に遷移します。
少し待つと操作できるようになります。

これでCloud9環境の構築は完了です。

20201011024006.png

次はCloud9での開発に適した設定変更をします。

スペース可視化

スペース記号の可視化を行います。

テキストエディタの右下にある⚙歯車マークをクリックします。
設定が表示されるのでShow Invisiblesをクリックします。

20201011024747.png

Ruby 環境構築

ここからはRubyの環境構築をしていきます。

Cloud9画面下部にターミナルが表示されています。
ここでコマンドを実行していきます。

20201011042946.png

まずはOSにプリインストールされているライブラリを最新化します。
基本的にアップデートされるものは無いと思います。

sudo yum update

20201011025015.png

rvmを使用してrubyのバージョンを切り替える

今回はRVM(Ruby Version Manager)を使用して複数VersionのRubyを管理したいと思います。

rvmインストール

Clooud9にはrvmがプリインストールされています。
念のためrvmがインストールされていることを確認します。

rvm -v

20201011025047.png

ruby-2.5.1インストール

現段階でrvmで切り替えできるRubyのバージョンを確認します。

rvm list

20201011025113.png

ruby-2.6.3が使用できるようです。
今回はruby-2.5.1を使用したいのでrvmを使ってインストールします。

rvm install 2.5.1

20201011025150.png

インストールが完了したらもう一度切り替えできるRubyのバージョンを確認します。

rvm list

20201011025207.png

ruby-2.5.1が追加され、currentとなっていることが確認できます。

念のため、Rubyコマンドでも確認します。

ruby -v

20201011025228.png

rvmデフォルトバージョン変更

これでRVMを使用したRubyのバージョン切り替えができました。
ただ、今の設定ではターミナルを再起動した時にはrubyバージョンが2.6.3に戻ってしまいます。

そのため、RVMでのデフォルトバージョンを2.5.1に変更します。

rvm --default use 2.5.1

20201011025304.png

切り替えできるRubyのバージョンを確認します。

rvm list

20201011025334.png

ruby-2.5.1がcurrent && defaultとなっていることが確認できます。

以上でrubyの環境構築は完了です。

Rails 環境構築

ここからはRailsの環境構築をしていきます。

railsインストール確認

まずはrailsコマンドがインストールされているかを確認します。

rails -v

20201011025407.png

インストールされていないことが確認できます。

gemインストール

Railsはgemというコマンドを使用してインストールします。
gemとはRuby applicationやライブラリーのパッケージです。

gemがインストールされているか確認します。
rubyがインストールされていれば使用できます。

gem -v

20201011025430.png

インストールされていることが確認できます。

railsインストール

それでは、gemを使用してrailsをインストールします。

gem install rails -v 5.2.1 -N

-Nオプションを使用すると、諸々のドキュメントのインストールをスキップできます。(インストールが早くなる)

20201011025538.png

インストールが完了したらrailsのバージョンを確認します。

rails -v

20201011025601.png

インストールされていることが確認できました。

Railsの環境構築は以上です。

最後に

Ruby on Railsの環境構築って割と面倒臭いイメージがありましたが、(特にWindows)Cloud9を使用すると簡単に環境構築ができますね。
これからRuby on Railsを使っていくつか簡単なシステムを構築してみようと思います。

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

DBをSQLiteからMySQLに変更する

はじめに

モデル作ってマイグレートして、シークエルプロでDB確認したら

作ったつもりのデータベースがない!となったので、備忘録として対処法を載せときます。

解決法

こちらの記事を参考に対処したところ、解決に至りました。

やったこと

gemfileの修正

初歩的ミスですが、下画像の選択部分(gemfileのSQLite3)をコメントアウトします。
スクリーンショット 2020-10-12 6.06.03.png

ターミナルにて

bundle install --without production

DBを作る必要があるので

bundle exec rake db:create

そして、これは必要あったのかな?既にやっていたので不要だったかも

rails db:migrate

できた

シークエルプロにて、データベースが確認できるようになりました。
スクリーンショット 2020-10-12 6.18.21.png

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

rails db:create実行時のFATAL: role "admin0" does not existとPG::ConnectionBad: FATAL: role "admin0" does not existの対処方

rails db:create実行時にエラーが出た際の解決方法について後発者のために解決方法のまとめを書いておく。

実行環境

windows 10 home
ubuntu 20.04 LTS
ruby 2.7.1
Rails 6.0.3
postgresql 11

エラー文

$ rails db:create
FATAL:  role "admin0" does not exist
Couldn't create 'taskleaf2_development' database. Please check your configuration.
rails aborted!
PG::ConnectionBad: FATAL:  role "admin0" does not exist
/home/admin0/taskleaf2/bin/rails:9:in `<top (required)>'
/home/admin0/taskleaf2/bin/spring:15:in `<top (required)>'
bin/rails:3:in `load'
bin/rails:3:in `<main>'
Tasks: TOP => db:create
(See full trace by running task with --trace)

今回要所となるのは上から5行目まであたりだろうか。

エラー解決順序

以前私は同じようなエラーに出会っていて既に解決したことがあったので同じ方法をとってみた。
https://teratail.com/questions/297341

$ yarn install

だがこれでも解決できなかった。

エラーがPG::ConnectionBad:である点に着目してみることにする。
だが以下のコマンドのようにDBは起動しているはずだし、、、?

$ sudo service postgresql start
[sudo] admin0 のパスワード:
 * Starting PostgreSQL 11 database server [ OK ]
 * Starting PostgreSQL 13 database server [ OK ]

別(下)の手段で起動にチャレンジしてみることにした。

$ sudo su - postgres
 \q

再度rails db:createを実行したところエラー文に変化が現れた。

$ rails db:create
WARNING:  could not flush dirty data: Function not implemented
Created database 'taskleaf2_development'
WARNING:  could not flush dirty data: Function not implemented
Created database 'taskleaf2_test'

あとはこのエラー文で検索したところ
https://stackoverflow.com/questions/45437824/postgresql-warning-could-not-flush-dirty-data-function-not-implemented
こちらが有力そうだったので
/etc/postgresql/11/main/postgresql.confの内容の一部を以下のように書き換えた

fsync = off
data_sync_retry = true

再度rails db:createを実行してみる

rails db:create
Database 'taskleaf2_development' already exists
Database 'taskleaf2_test' already exists

どうやら成功したようだ。
サーバーも起動できたので今回はこれで問題解決したと判断する。

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