- 投稿日:2021-02-26T23:49:28+09:00
プルダウンの作り方
- 投稿日:2021-02-26T21:28:45+09:00
【Rails】ページネーションの実装
ページネーションとは
一覧表示画面に表示するレコード件数が多くなると、目視での確認が困難になってしまいます。この問題を避けるための方法がページネーションです。
ページネーションとは、レコード件数が一定数を超えた場合に複数のページに分割して表示を行うことです。kaminariのインストール
Railsでページネーションを実現するgemとして、kaminariがあります。今回はこれを用いてページネーションを進めていきます。
まずは、gemファイルに以下を追加。Gemfilegem 'kaminari'bundleコマンドでkaminari gemをインストールします。
$ bundleページ番号に対応する範囲のデータを検索するようにする
PostsControllerのindexアクションを変更します。
このアクションでは、ページ番号がparams[:page]として渡されることを前提とします。ページ番号を用いて、データ範囲を検索する機能は、kaminariのpageスコープで簡単に行えます。
indexアクションを以下のように変更します。app/controllers/posts_controller.rbdef index @posts =Post.page(params[:page]) endビューにページネーションのための情報を表示する
ビューには、ページネーションための以下の情報を表示するようにします。
- 現在どのページを表示しているかの情報
- 他のページに移動するためのリンク
- 全データが何件なのかという情報
1と2のためにはpaginate、3のためにはpage_entries_infoというkaminariヘルパーメソッドが利用できます。
それでは、ビューを以下のように変更します。
… = link_to '新規投稿', new_post_path, class: 'btn btn-primary' .mb-3 =paginate @posts =page_entries_info @posts table.table.table-hover …ページネーションの情報を表示する枠として、mb-3というCSSクラスのついたdiv要素を用意し、paginateヘルパー、page_entries_infoヘルパーを呼び出しています。
翻訳ファイルの追加
kaminariが用意している翻訳ファイルはen(英語)のみなので、以下の内容でjaの翻訳ファイルを用意します。
config/kaminari.ja.ymlja: views: pagination: first: "« 最初" last: "最後 »" previous: "‹ 前" next: "次 ›" truncate: "…" helpers: page_entries_info: one_page: display_entries: zero: "%{entry_name}がありません" one: "1件の%{entry_name}が表示されています" other: "&{count}件の%{entry_name}が表示されています" more_pages: display_entries: "全%{total}件中 %{first} - %{last}件の%{entry_name}が表示されています"デザインの調整
機能は実装できましたが、見た目がいまいちなので修正します。paginateヘルパーが表示に使用するパーシャルテンプレートをアプリ内に用意し、それを自由にカスタマイズすることができます。パーシャルテンプレートはkaminariの提供するジェネレータで生成しますが、その際にテーマ(Thema)と呼ばれるデザインの種類を指定して、好みのパーシャルテンプレートを生成できます(https://github.com/amatsuda/kaminari_themes)。
今回はbootstrap4というテーマのパーシャルテンプレートを生成します。
$ bin/rails g kaminari:views bootstrap4app/views/kaminari配下にいくつかビューテンプレートが追加されます。
無事に格好良くなっています!
参考
- 投稿日:2021-02-26T21:19:32+09:00
Couldn't find User without an ID エラーを解決した
rails初学者です。
現在オリジナルアプリを開発しております。
ユーザー詳細機能を実装しようとして、ユーザー詳細画面へのリンクを設定し、動作確認してみたら
「Couldn't find User without an ID」
というエラーが発生したため、エラー解決をしました。環境
ruby '2.6.5'
rails '6.0.0'ビュー
<%= link_to "#{current_user.name}", user_path(current_user) %>このように、ログイン中のユーザーの名前を表示し、名前にユーザー詳細ページへ飛ぶリンクを設定しました。
Usersコントローラー
def show @user = User.find(params[:id]) endUsersコントローラーのshowアクションで、パラメータのidを受け取って
そのidに合致するユーザーを@userに格納しました。挙動確認
予想される挙動は、「ユーザー名をクリックするとユーザー詳細ページに遷移する」
ですが、いざクリックしたところ、Usersコントローラーのshowアクションで下記のようなエラーメッセージが出ました。ActiveRecord::RecordNotFound in UsersController#show Couldn't find User without an ID簡単に言うとIDがありませんよ、ということです。
パラメーターの確認
showアクションにbinding.pryを仕込み、パラメーターを確認してみました。
すると下記のような結果でした。pry(#<UsersController>)> params => <ActionController::Parameters {"controller"=>"users", "action"=>"show", "format"=>"1"} permitted: false>本来ならばid="1"と出て欲しいところが、format="1"となっており
パラメーターにidが含まれていないようです。結論
ルーティングの記述にミスがありました。
resource :users, only: :show'resources'と書かなければならないところを、'resource'と書いてしまっておりました。
resourceメソッドはresourcesメソッドに対して、indexアクションとid付パスを生成してくれないみたいです。
そのため、パラメーターにidが含まれていませんでした。解決策
ルーティングを
resources :users, only: :showと書き直しました。
再度挙動を確かめたところ、問題なくユーザー詳細ページに遷移しました。
参考
- 投稿日:2021-02-26T21:13:41+09:00
EC2インスタンスにRails + MySQL環境構築
はじめに
AWS
のEC2インスタンス
立ち上げたところからのサーバー構築手順です。・設定用のツールをインストール
・Node.jsをインストール
・Yarnをインストール
・rbenvとruby-buildをインストール
・Rubyをインストール
・MariaDBをインストール(サーバーにDB設定する場合)
・Githubとの接続
・Unicornをインストール
・Githubからコードをクローン
・gemをインストール
・環境変数の設定
・アセットファイルをコンパイル
・本番環境でRailsを起動ここまではアプリケーションサーバー構築。
WEBサーバー構築は以下。
EC2サーバーにRails + Nginx設定用のツールをインストール
yum
というコマンドを使ってこのサーバに元々あるプログラムをアップデートします。$ sudo yum -y update環境構築に必要なパッケージを諸々インストール。(長いコマンド)
$ sudo yum -y install git make gcc-c++ patch libyaml-devel libffi-devel libicu-devel zlib-devel readline-devel libxml2-devel libxslt-devel ImageMagick ImageMagick-devel openssl-devel libcurl libcurl-devel curlNode.jsをインストール
サーバーサイドで動く
JavaScript
のパッケージです。$ sudo curl -sL https://rpm.nodesource.com/setup_10.x | sudo bash - $ sudo yum -y install nodejsYarnをインストール
Rails
に搭載されているJavaScriptのパッケージを管理するためのパッケージマネージャです。$ sudo yum -y install wget $ sudo wget https://dl.yarnpkg.com/rpm/yarn.repo -O /etc/yum.repos.d/yarn.repo $ sudo yum -y install yarnrbenvとruby-buildをインストール
rbenv
とruby-build
は、Ruby
のバージョンを管理する際に組み合わせて使うツールになります。これらはRuby
をインストールする前に、インストールする必要があります。
ruby-build
はrbenv
のプラグインであり、ruby-build
によってRuby
の様々なバージョン(2.0.0など)をインストールすることができます。
rbenv
を使用することでruby
のバージョンを切り替えることできます。#rbenvのインストール $ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv #パスを通す(どのディレクトリからもアプリケーションを呼び出せる状態にする) $ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile #rbenvを呼び出すための記述 $ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile #.bash_profileの読み込み $ source .bash_profile #ruby-buildのインストール $ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build #rehashを行う $ rbenv rehashRubyをインストール
Ruby
のインストールには時間がかかります。$ rbenv install 2.6.5 $ rbenv global 2.6.5 #EC2インスタンス内で使用するRubyのバージョンを決める $ rbenv rehash #rehashを行う $ ruby -v # バージョンを確認※
AWS RDS
を使用する場合はMariaDB関連操作
は不要。MariaDBをインストール(サーバーにDB設定する場合)
Amazon Linux 2
を利用している場合、MariaDB
はyum
コマンドからインストールすることができます。$ sudo yum -y install mysql56-server mysql56-devel mysql56 mariadb-server mysql-devel○○○利用できませんと表示されますが問題ありません。
データベースを起動
データベースを起動するために
systemctl
コマンドを利用します。これは、Amazon Linux
やCentOS
に含まれているもので、インストールしたソフトウェアの起動を一括して行えるツールです。$ sudo systemctl start mariadb $ sudo systemctl status mariadb #起動できたか確認データベースのrootパスワードの設定
yum
でインストールしたMariaDB
には、デフォルトで root というユーザーでアクセス出来るようになっていますが、パスワードは設定されていません。パスワードを設定していきます。$ sudo /usr/bin/mysql_secure_installation対話形式でパスワードを設定します。
データベースへの接続確認
$ mysql -u root -pパスワードを入力するように表示されるので、先程設定したパスワードを入力して、Enterしてください。
Githubとの接続
EC2サーバのSSH鍵ペアを作成
$ ssh-keygen -t rsa -b 4096 Generating public/private rsa key pair. Enter file in which to save the key (/home/ec2-user/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/ec2-user/.ssh/id_rsa. Your public key has been saved in /home/ec2-user/.ssh/id_rsa.pub. The key fingerprint is: 3a:8c:1d:d1:a9:22:c7:6e:6b:43:22:31:0f:ca:63:fa ec2-user@ip-172-31-23-189 The key's randomart image is: +--[ RSA 4096]----+ | + | | . . = | | = . o . | | * o . o | |= * S | |.* + . | | * + | | .E+ . | | .o | +-----------------+全て何も入力せずにEnterキーで進んでください。
生成されたSSH公開鍵を表示し、値をコピーします。
$ cat ~/.ssh/id_rsa.pub ssh-rsa AAAAB3NzaC1yc2E......catで表示させた公開鍵を、Githubにアクセスして登録していきます。
https://github.com/settings/keys①Titleは自由に記入
②Keyに公開鍵をペースト
③Add SSH Key
を押して保存Githubに鍵を登録できたら、SSH接続できるか以下のコマンドで確認
$ ssh -T git@github.com Hi <Githubユーザー名>! You've successfully authenticated, but GitHub does not provide shell access.Permission denied (publickey). と表示された場合は、SSH鍵の設定が間違っているので、作業を確認してください。
アプリケーションサーバの設定
アプリケーションサーバとは、ブラウザからの「リクエスト」を受け付けRailsアプリケーションを実際に動作させるソフトウェアのことです。
Unicornをインストール
ローカル環境のGemfile を編集
group :production do gem 'unicorn', '5.4.1' endローカルのターミナルで
% bundle installconfig/unicorn.rbを作成し、内容を以下のように編集
#サーバ上でのアプリケーションコードが設置されているディレクトリを変数に入れておく app_path = File.expand_path('../../', __FILE__) #アプリケーションサーバの性能を決定する worker_processes 1 #アプリケーションの設置されているディレクトリを指定 working_directory app_path #Unicornの起動に必要なファイルの設置場所を指定 pid "#{app_path}/tmp/pids/unicorn.pid" #ポート番号を指定 listen 3000 #エラーのログを記録するファイルを指定 stderr_path "#{app_path}/log/unicorn.stderr.log" #通常のログを記録するファイルを指定 stdout_path "#{app_path}/log/unicorn.stdout.log" #Railsアプリケーションの応答を待つ上限時間を設定 timeout 60 #以下は応用的な設定なので説明は割愛 preload_app true GC.respond_to?(:copy_on_write_friendly=) && GC.copy_on_write_friendly = true check_client_connection false run_once = true before_fork do |server, worker| defined?(ActiveRecord::Base) && ActiveRecord::Base.connection.disconnect! if run_once run_once = false # prevent from firing again end old_pid = "#{server.config[:pid]}.oldbin" if File.exist?(old_pid) && server.pid != old_pid begin sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU Process.kill(sig, File.read(old_pid).to_i) rescue Errno::ENOENT, Errno::ESRCH => e logger.error e end end end after_fork do |_server, _worker| defined?(ActiveRecord::Base) && ActiveRecord::Base.establish_connection endGithubからコードをクローン
EC2サーバーにSSH接続後に以下の操作
#mkdirコマンドで新たにディレクトリを作成 $ sudo mkdir /var/www/ #作成したwwwディレクトリの権限をec2-userに変更 $ sudo chown ec2-user /var/www/ #移動 $ cd /var/www/ #Githubから「リポジトリURL」を取得しクローンします $ git clone https://github.com/<ユーザー名>/<リポジトリ名>.gitSwap(スワップ)領域作成
Swap領域は、メモリが使い切られそうになった時にメモリの容量を一時的に増やすために準備されるファイルです。
EC2はデフォルトではSwap領域を用意していないため、これを準備することでメモリ不足の処理エラーを防ぎます。
$ cd $ sudo dd if=/dev/zero of=/swapfile1 bs=1M count=512 # しばらく待って、以下のように表示されれば成功 512+0 レコード入力 512+0 レコード出力 536870912 バイト (537 MB) コピーされました、 7.35077 秒、 73.0 MB/秒 $ sudo chmod 600 /swapfile1 $ sudo mkswap /swapfile1 # 以下のように表示されれば成功 スワップ空間バージョン1を設定します、サイズ = 524284 KiB ラベルはありません, UUID=74a961ba-7a33-4c18-b1cd-9779bcda8ab1 $ sudo swapon /swapfile1 #右へ長いコマンドです $ sudo sh -c 'echo "/swapfile1 none swap sw 0 0" >> /etc/fstab'gemをインストール
rbenvでインストールされたRubyが使われているかチェック
$ cd /var/www/<アプリ名> $ ruby -v ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-linux]本番環境でgemを管理するためのbundlerをインストールして、bundle installを実行
ローカル環境でバージョン確認。
#ローカル環境 アプリのディレクトリで以下を実行 % bundler -v Bundler version 2.1.4ローカルと同じバージョンをEC2サーバー上で以下のコマンド
# ローカルで確認したbundlerのバージョンを導入する $ gem install bundler -v 2.1.4 $ bundle install環境変数の設定
環境変数は、Railsからは
ENV['<環境変数名>']
という記述でその値を利用することができます。今回は、SECRET_KEY_BASE
という環境変数を指定していきます。$ rake secret 69619d9a75b78f2e1c87ec5e07541b42f23efeb6a54e97da3723de06fe74af29d5718adff77d2b04b2805d3a1e143fa61baacfbf4ca2c6fcc608cff8d5a28e8dこの長い英数の羅列は、この後利用するのでコピーしておきます。
環境変数は
/etc/environment
というファイルに保存することで、サーバ全体に適用されます。$ sudo vim /etc/environment#前章で設定したデータベースのrootユーザーのパスワードを入力 DATABASE_PASSWORD='データベースのrootユーザーのパスワード' SECRET_KEY_BASE='先程コピーしたsecret_key_base'書き込みができたら esc(エスケープキー)を押下後、
:wq
と入力して内容を保存します。
保存できたら環境変数を適用するために一旦ログアウト。$ exit logout Connection to 52.xx.xx.xx closed.再度ログイン。
$ ssh -i [ダウンロードした鍵の名前].pem ec2-user@[作成したEC2インスタンスと紐付けたElastic IP]環境変数が適用されているか確認
$ env | grep SECRET_KEY_BASE SECRET_KEY_BASE='secret_key_base' $ env | grep DATABASE_PASSWORD DATABASE_PASSWORD='データベースのrootユーザーのパスワード'ポートを開放
config/unicorn.rb
にlisten 3000
と記述しましたが、これはRailsのサーバを3000番ポートで起動するということを意味します。EC2インスタンスの
セキュリティグループ
を編集しカスタムTCP
でポート3000を許可してください。database.ymlの本番環境の設定
config/database.yml(ローカル)
を以下のように設定。<<: *default database: ~~~(それぞれのアプリによって異なる) username: root password: <%= ENV['DATABASE_PASSWORD'] %> socket: /var/lib/mysql/mysql.sock※
RDS
など使用の場合はまた異なります。ローカルでの編集をコミットして、GitHubにプッシュし、EC2へ再度クローンします。
$ git pull origin masterデータベースを作成しマイグレーションを実行
EC2サーバー上で以下のコマンド。
$ rails db:create RAILS_ENV=production Created database '<データベース名>' $ rails db:migrate RAILS_ENV=productionもしデータベースが起動していないときは
sudo systemctl start mariadbアセットファイルをコンパイル
本番モードでは、事前にアセットをコンパイルする必要があります。
$ rails assets:precompile RAILS_ENV=production本番環境でRailsを起動
unicorn のgemをインストールしていると
unicorn_rails
というコマンドが使えるようになっています.
ローカルでいうrails s
にあたります。Unicornのプロセスを確認
$ ps aux | grep unicorn ec2-user 17877 0.4 18.1 588472 182840 ? Sl 01:55 0:02 unicorn_rails master -c config/unicorn.rb -E production -D ec2-user 17881 0.0 17.3 589088 175164 ? Sl 01:55 0:00 unicorn_rails worker[0] -c config/unicorn.rb -E production -D ec2-user 17911 0.0 0.2 110532 2180 pts/0 S+ 02:05 0:00 grep --color=auto unicorn「unicorn_rails master」と表示されているプロセスがあれば
kill
コマンドで停止してください。$ kill <確認したunicorn rails masterのPID>unicornを起動
$ cd /var/www/[リポジトリ] $ RAILS_SERVE_STATIC_FILES=1 unicorn_rails -c config/unicorn.rb -E production -D
RAILS_SERVE_STATIC_FILES=1
は、コンパイルされたアセットをRailsが見つけられるような指定です。ブラウザで確認
http://<Elastic IP>:3000/で正常に表示されれば成功です。
うまく表示されない場合
エラーが起こっているのでログを確認。
$tail -f /var/www/<アプリ名>/log/production.logまたは/var/www/<リポジトリ名>/log/unicorn.stderr.logをlessまたはcatコマンドで確認。
設定は以上です。
- 投稿日:2021-02-26T21:10:16+09:00
Rails「paramsの使用法」
はじめに
今回、テキストの一覧ページを作成する際、link_toとhelperよりparamsの使用方法を復習できたので共有します。
やりたいこと
・viewで各教材の一覧ページへのリンクを設定する
・controllerで各教材の一覧ページに適した教材を取得する
・helperで一覧ページのタイトルを表示する処理を書くviewで各教材の一覧ページへのリンクを設定する
_header.html.erb<%= link_to "Ruby/Rails教材", texts_path, class: "dropdown-item" %> <%= link_to "AWS講座", texts_path(genre: "AWS"), class: "dropdown-item" %> <%= link_to "PHPテキスト教材", texts_path(genre: "Php"), class: "dropdown-item" %><%= link_to "表示名",次のアクションへ遷移するパス(パラメーター),class: "クラス名" %>
となるよう記述します。各教材の一覧ページへのパスtext_pathにパラメーターとしてgenreを指定することで、表示する教材を制限します。
controllerに条件分岐を記述
texts_controller.rbdef index if params[:genre] == nil @texts = Text.where(genre: ["Basic", "Git", "Ruby", "Ruby on Rails"]) else @texts = Text.where(genre: params[:genre]) end endパラメーターgenreの指定がない場合とある場合で条件分岐し、条件にあった教材だけ取得するようにします。
helperに変換部分を作り教材毎にページタイトルが変わるようにする
texts_helper.rbmodule TextsHelper def page_title if params[:genre] == nil "Ruby/Rails" else params[:genre] end end endhelperにpage_titleというメソッドを定義し、パラメーターgenreの指定がない場合はRuby/Railsを表示し、指定がある場合はその教材のgenre名を表示するよう処理を書きます。
helperは、定義しておくことでview内でその処理を呼び出すことができ、viewをよりシンプルに書くことができます。
一覧ページのview
index.html.erb<div class="text-title"> <h3><%= page_title %>テキスト教材</h3> </div> <% @texts.each do |text| %> <div class="card-body"> <p class="card-text"><%= text.title %></p> <p class="card-text"><%= text.genre %></p> </div> <% end %>viewで <%= page_title(helperで定義したメソッド) %> を記述し、ページ毎にタイトルを表示するようにします。
controllerで定義した@textsはeachの繰り返し処理でtitleとgenreを表示するようにします。
まとめ
このようにcontrollerとhelperでパラメーターを元に条件分岐することで、一覧ページごとに適したタイトルとテキストを表示することができました。
- 投稿日:2021-02-26T20:16:54+09:00
MacbookにRuby導入
環境
MacOS:Catalina
MacbookPro
初期の状態からRuby
、Rails
、MySQL
をインストールします。手順は以下です。
・Zshをデフォルトに
・Command line tools導入
・Homebrew導入
・Rubyインストール
・MySQLを導入
・Railsを導入
・Node.jsの導入
・yarnの導入Zshをデフォルトに
# zshをデフォルトに設定 % chsh -s /bin/zsh # ログインシェルを表示 % echo $SHELL # 以下のように表示されれば成功 /bin/zshパスワード求められたらPCのパスワードを入力します。
Command line tools導入
以下をターミナルで
% xcode-select --installインストールをクリックして進める。
Homebrew導入
Homebrewというソフトウェア管理ツールを導入します。
以下をターミナルで。% cd # ホームディレクトリに移動 % pwd # ホームディレクトリにいるかどうか確認 % /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" # コマンドを実行PCパスワードを求められます。
途中エンターキー入力あります。以下でインストールできたか確認。
% brew -v最新の状態にします。
% brew updateHomebrewの権限変更
% sudo chown -R `whoami`:admin /usr/local/binRubyインストール
Rubyの土台となるrbenvとruby-buildを、Homebrewを用いてインストール
% brew install rbenv ruby-buildrbenvをどこからも使用できるようにする。
% echo 'eval "$(rbenv init -)"' >> ~/.zshrczshrcの変更を反映
% source ~/.zshrcターミナルのirb上で日本語入力を可能にする設定を行うために、以下のコマンドでインストール
% brew install readlineすでにインストール済と出る場合あります。
readlineをどこからも使用できるようにします。
% brew link readline --forcerbenvを利用してRubyをインストール。
% RUBY_CONFIGURE_OPTS="--with-readline-dir=$(brew --prefix readline)" % rbenv install 2.6.5時間がかかるコマンドです。
rbenvを読み込んで変更を反映。
% rbenv rehashRubyのバーションを確認。
% ruby -vMySQLを導入
% brew install mysql@5.6MySQLの自動起動設定。
MySQLは本来であればPC再起動のたびに起動し直す必要がありますが、それは面倒であるため、自動で起動するように。% mkdir ~/Library/LaunchAgents % ln -sfv /usr/local/opt/mysql\@5.6/*.plist ~/Library/LaunchAgents % launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql\@5.6.plistmysqlコマンドをどこからでも実行できるように。
% echo 'export PATH="/usr/local/opt/mysql@5.6/bin:$PATH"' >> ~/.zshrc # mysqlのコマンドを実行できるようにする設定 % source ~/.zshrc # 設定を読み込むコマンド % which mysql # mysqlのコマンドが打てるか確認する # 以下のように表示されれば成功 /usr/local/opt/mysql@5.6/bin/mysqlMySQLの起動を確認。
% mysql.server status # MySQLの状態を確認するコマンド # 以下のように表示されれば成功 SUCCESS! MySQL runningRailsを導入
Rubyの拡張機能(gem)を管理するためのbundler(バンドラー)をインストールします。
% gem install bundler --version='2.1.4'Railsをインストール。
% gem install rails --version='6.0.0'処理に時間かかります。
rbenvを再読み込み。
% rbenv rehashRailsが導入できたか確認。
% rails -v Rails 6.0.0 # 「Rails」のあとに続く数字は変わる可能性がありますNode.jsの導入
Railsを動かすためにはNode.jsが必要となり、それをHomebrewを用いてインストールします。
% brew install node@14Warningでても問題ないです。
Node.jsへのパスを設定。
% echo 'export PATH="/usr/local/opt/node@14/bin:$PATH"' >> ~/.zshrc % source ~/.zshrcNode.jsが導入できたか確認。
% node -v v14.15.3 # 数値は異なる場合がありますyarnの導入
yarnのインストール。
% brew install yarnyarnが導入できたか確認。
% yarn -v以上で、Railsでアプリ作成ができる環境が整いました。
- 投稿日:2021-02-26T18:57:10+09:00
*.js.erbファイルは読み込めているのに表示されないときの対処方法
- 「Completed 200 OK」となっているのに*.js.erbに書いたjavascriptのコードが動かないとき。
- javascriptのエラーも発生していないとき。
原因
https://github.com/rails/rails/issues/33115
どうやらrails-ujsが求めているContent-Typeが一致しないことが原因っぽいです。対策
【対策1】レイアウトファイル名を変更する
views/layouts/*.erb
↓
views/layouts/*.html.erb
【対策2】render時に
layout: false
を指定する*_controller.rbdef create @sending_user = User.find(current_user.id) @receiving_user = User.find(params[:user_id]) new_like_balance = @sending_user.like_balance - 1 @sending_user.update(like_balance: new_like_balance) @like = Like.create(sender_id: @sending_user.id, receiver_id: @receiving_user.id) # ここ render formats: :js, layout: false endどちらの方法でも解決できました。
- 投稿日:2021-02-26T18:56:30+09:00
Active Storageで追加した画像をjavascriptを使ってプレビューする方法
はじめに
active Storageを使って保存する画像を保存前に表示させて確認したいと思って調べたもののまとめです。
前提
すでに画像保存機能は出来上がっている
javascriptファイルの追加
app/javascript/packsの中にプレビュー用のファイルを追加します。今回、名前はpreview.jsとします。
preview.jsの読み込み
前述で追加したpreview.jsをpacks内application.jsで読み込みます。
app/javascript/packs/application.jsrequire('./preview')読み込まれたときに動作する関数を定義
まずは動作ようの記述を。
app/javascript/packs/preview.jsdocument.addEventListener('DOMContentLoaded', function(){ });画像を表示するビューファイルに画像表示スペースをつくる
app/views/messages/_form.html.erb
<div id="image-list"></div>HTMLの要素を、JavaScript側で取得
続いて、今記述した画像を表示するためのHTMLの要素を、JavaScript側で取得します。
app/javascript/packs/preview.jsdocument.addEventListener('DOMContentLoaded', function(){ const ImageList = document.getElementById('image-list'); });input要素で値の変化が起こったときに呼び出される関数を定義
document.addEventListener('DOMContentLoaded', function(){ const ImageList = document.getElementById('image-list'); document.getElementById('message_image').addEventListener('change', function(e){ console.log(222); }); });念の為ファイルを選択する」をクリックして適当な画像を選択し、以下のように222と表示できているかを確認しておきましょう。
inputの中にある画像を取得
発火したイベントeの中の、targetの中の、filesという配列に格納されています。
次に取得した画像の情報は、変数fileとして宣言します。
app/javascript/packs/preview.jsdocument.addEventListener('DOMContentLoaded', function(){ const ImageList = document.getElementById('image-list'); document.getElementById('message_image').addEventListener('change', function(e){ const file = e.target.files[0]; }); });URLオブジェクトのcreateObjectURLメソッドを呼び出し、変数fileを引数として渡す
次はcreateObjectURLメソッドを引数として渡します。
app/javascript/packs/preview.jsdocument.addEventListener('DOMContentLoaded', function(){ const ImageList = document.getElementById('image-list'); document.getElementById('message_image').addEventListener('change', function(e){ const file = e.target.files[0]; const blob = window.URL.createObjectURL(file); }); });実際に画像を表示できるようにする。
preview.jsを以下のように編集して画像を表示します。
document.addEventListener('DOMContentLoaded', function(){ const ImageList = document.getElementById('image-list'); document.getElementById('message_image').addEventListener('change', function(e){ const file = e.target.files[0]; const blob = window.URL.createObjectURL(file); // 画像を表示するためのdiv要素を生成 const imageElement = document.createElement('div'); // 表示する画像を生成 const blobImage = document.createElement('img'); }); });画像URLをimg要素のsrc属性に設定
setAttribute()というdocumentオブジェクトを用いて、画像情報をsrc属性に指定します。
app/javascript/packs/preview.jsdocument.addEventListener('DOMContentLoaded', function(){ const ImageList = document.getElementById('image-list'); document.getElementById('message_image').addEventListener('change', function(e){ const file = e.target.files[0]; const blob = window.URL.createObjectURL(file); // 画像を表示するためのdiv要素を生成 const imageElement = document.createElement('div'); // 表示する画像を生成 const blobImage = document.createElement('img'); blobImage.setAttribute('src', blob); }); });appendChild()
指定した親要素の中に要素を追加するappendChildメソッドを使って、HTML要素を追加します。
app/javascript/packs/preview.jsdocument.addEventListener('DOMContentLoaded', function(){ const ImageList = document.getElementById('image-list'); document.getElementById('message_image').addEventListener('change', function(e){ const file = e.target.files[0]; const blob = window.URL.createObjectURL(file); // 画像を表示するためのdiv要素を生成 const imageElement = document.createElement('div'); // 表示する画像を生成 const blobImage = document.createElement('img'); blobImage.setAttribute('src', blob); // 生成したHTMLの要素をブラウザに表示させる imageElement.appendChild(blobImage); ImageList.appendChild(imageElement); }); });条件分岐でトップページのエラーを解決
このままではpreview.jsで指定している要素が、新規投稿ページや編集ページにだけ存在し、TOP画面などでは表示されないため、エラーが発生します。
そのため、下記のようにして条件分岐してエラーを起こさないようにします。
app/javascript/packs/preview.jsif (document.URL.match( /new/ ) || document.URL.match( /edit/ )) { document.addEventListener('DOMContentLoaded', function(){ const ImageList = document.getElementById('image-list'); document.getElementById('message_image').addEventListener('change', function(e){ const file = e.target.files[0]; const blob = window.URL.createObjectURL(file); // 画像を表示するためのdiv要素を生成 const imageElement = document.createElement('div'); // 表示する画像を生成 const blobImage = document.createElement('img'); blobImage.setAttribute('src', blob); // 生成したHTMLの要素をブラウザに表示させる imageElement.appendChild(blobImage); ImageList.appendChild(imageElement); }); }); }表示画像を編集可能にする。
最後に既存のプレビューを削除した後に新しい画像のプレビューを表示することで画像を編集可能にして起きます。
if (document.URL.match( /new/ ) || document.URL.match( /edit/ )) { document.addEventListener('DOMContentLoaded', function(){ const ImageList = document.getElementById('image-list'); document.getElementById('message_image').addEventListener('change', function(e){ // 画像が表示されている場合のみ、すでに存在している画像を削除する const imageContent = document.querySelector('img'); if (imageContent){ imageContent.remove(); } const file = e.target.files[0]; const blob = window.URL.createObjectURL(file); const imageElement = document.createElement('div'); const blobImage = document.createElement('img'); blobImage.setAttribute('src', blob); imageElement.appendChild(blobImage); ImageList.appendChild(imageElement); }); }); }完成
以上で完成です?
- 投稿日:2021-02-26T15:51:55+09:00
初めてのRuby On Rails
Ruby On Railsとは
オープンソースのWebアプリケーションフレームワークである。RoRまたは単にRailsと呼ばれる。その名にも示されているようにRubyで書かれている。またModel View Controller(MVC)アーキテクチャに基づいて構築されている。
Ruby_on_Rails - Wikipedia初めてRubyに触れるので、Progateを使って学習してみました。忘れそうなことや頭を整理するために、記載しています。
Ruby on Rails5 - ProgateRailsアプリケーションの作成
以下コマンドにより必要なファルダ等が作成される
ターミナルrails new app_name
ツリー構造
app_name/
├ app/
├ config/
├ db/
└ その他サーバーの起動
ターミナルrails server
アプリ表示
サーバーを起動後に、ブラウザで
localhost:3000
にアクセストップページの作成(新しくページを作るコマンド)
以下のコマンドにより自動で
top
というページが作成されるターミナルrails generate controller home top
home: コントローラー名
top: アクション名※すでにhomeコントローラーがある場合には別アクションでは使用不可
トップ画面
localhost:3000/home/top
にアクセスできるページを表示するのに必要な3ファイル
- ビュー(view)
- コントローラー(controller)
- ルーティング(routing)
ビュー(view)
ビューとは、ページの「見た目」を作るためのHTMLファイルである
ブラウザとRailsのやりとりの中で、Railsからビューが返され、ページが表示されるツリー構造
viewsフォルダの中に
homeフォルダ
とtop.html.erb
というファイルが作成されるapp/ アプリケーションのメインフォルダ
└ view/ ビューフォルダ
│ ┌──────────┐
└ │ home/ │
│ └ top.html.erb │
└──────────┘コントローラー(controller)
ページを表示するとき、Railsの中ではコントローラを経由してビューをブラウザに返している
ツリー構造
controllersフォルダの中に
home_controller.rb
というファイルが作成されるapp/ アプリケーションのメインフォルダ
└ controllers/ コントローラーフォルダ
│ ┌──────────┐
└ │ home_controller.rb │
└──────────┘ターミナルrails generate controller home top
上記実行時に
home_controller.rb
というコントローラのファイルが作成される
ファイルの中にtopメソッド
が追加される
コントローラ内のメソッドをアクション
と呼ぶhome_controller.rbclass HomeController < ApplicationController def top end endアクションは、コントローラと同じ名前のビューフォルダから、アクションと同じ名前のHTMLファイルを探してブラウザに返す
ルーティング(routing)
ルーティングはブラウザとコントローラを繋ぐ役割を担う
送信されたURLに対して「どのコントローラの、
どのアクション」で処理するかを決める「対応表」のことページが表示されるまでに、
ルーティング→コントローラ→ビュー
という順で処理トップ画面
localhost:3000/home/top
にアクセスした時を例に考えてみよう
- URL(home/top)に対応するHTMLファイルをリクエスト
ルーティング(対応表)
URL コントローラ アクション home/top home top ︙ ︙ ︙ 2.homeコントローラーのtopアクションを呼び出す
3.URLに対応したHTMLファイルを送信ツリー構造
configフォルダの中に
routes.rb
というファイルが作成されるconfig/ 設定情報に関するフォルダ
│ ┌────────┐
└ │ routes.rb │
└────────┘routes.rbRails.application.routes.draw do get "home/top" => "home#top" endまとめ
初めてRubyOnRailsを学習しました。
学習して、改めて整理すると意外とわかりやすいと思いました。これからも追加で記事を作成していきます。参考サイト
ディレクトリ構成図を書くときに便利な記号
https://qiita.com/paty-fakename/items/c82ed27b4070feeceff6
Markdown記法 サンプル集
https://qiita.com/tbpgr/items/989c6badefff69377da7
Qiitaのテーブルの書き方についてまとめた
https://qiita.com/zakuroishikuro/items/f33929eab9d55c5bd073
- 投稿日:2021-02-26T15:51:55+09:00
初めてのRuby On Rails その1
Ruby On Railsとは
オープンソースのWebアプリケーションフレームワークである。RoRまたは単にRailsと呼ばれる。その名にも示されているようにRubyで書かれている。またModel View Controller(MVC)アーキテクチャに基づいて構築されている。
Ruby_on_Rails - Wikipedia初めてRubyに触れるので、Progateを使って学習してみました。忘れそうなことや頭を整理するために、記載しています。
Ruby on Rails5 - ProgateRailsアプリケーションの作成
以下コマンドにより必要なファルダ等が作成される
ターミナルrails new app_name
ツリー構造
app_name/
├ app/
├ config/
├ db/
└ その他サーバーの起動
ターミナルrails server
アプリ表示
サーバーを起動後に、ブラウザで
localhost:3000
にアクセストップページの作成(新しくページを作るコマンド)
以下のコマンドにより自動で
top
というページが作成されるターミナルrails generate controller home top
以下のように
generate
をg
に省略可能ターミナルrails g controller home top
home: コントローラー名
top: アクション名※すでにhomeコントローラーがある場合には別アクションでは使用不可
トップ画面
localhost:3000/home/top
にアクセスできるページを表示するのに必要な3ファイル
- ビュー(view)
- コントローラー(controller)
- ルーティング(routing)
ビュー(view)
ビューとは、ページの「見た目」を作るためのHTMLファイルである
ブラウザとRailsのやりとりの中で、Railsからビューが返され、ページが表示されるツリー構造
viewsフォルダの中に
homeフォルダ
とtop.html.erb
というファイルが作成されるapp/ アプリケーションのメインフォルダ
└ view/ ビューフォルダ
│ ┌──────────┐
└ │ home/ │
│ └ top.html.erb │
└──────────┘コントローラー(controller)
ページを表示するとき、Railsの中ではコントローラを経由してビューをブラウザに返している
ツリー構造
controllersフォルダの中に
home_controller.rb
というファイルが作成されるapp/ アプリケーションのメインフォルダ
└ controllers/ コントローラーフォルダ
│ ┌──────────┐
└ │ home_controller.rb │
└──────────┘ターミナルrails generate controller home top
上記実行時に
home_controller.rb
というコントローラのファイルが作成される
ファイルの中にtopメソッド
が追加される
コントローラ内のメソッドをアクション
と呼ぶhome_controller.rbclass HomeController < ApplicationController def top end endアクションは、コントローラと同じ名前のビューフォルダから、アクションと同じ名前のHTMLファイルを探してブラウザに返す
ルーティング(routing)
ルーティングはブラウザとコントローラを繋ぐ役割を担う
送信されたURLに対して「どのコントローラの、
どのアクション」で処理するかを決める「対応表」のことページが表示されるまでに、
ルーティング→コントローラ→ビュー
という順で処理トップ画面
localhost:3000/home/top
にアクセスした時を例に考えてみよう
- URL(home/top)に対応するHTMLファイルをリクエスト
ルーティング(対応表)
URL コントローラ アクション home/top home top ︙ ︙ ︙ 2.homeコントローラーのtopアクションを呼び出す
3.URLに対応したHTMLファイルを送信ツリー構造
configフォルダの中に
routes.rb
というファイルが作成されるconfig/ 設定情報に関するフォルダ
│ ┌────────┐
└ │ routes.rb │
└────────┘routes.rbRails.application.routes.draw do get "home/top" => "home#top" endまとめ
初めてRubyOnRailsを学習しました。
学習して、改めて整理すると意外とわかりやすいと思いました。これからも追加で記事を作成していきます。参考サイト
ディレクトリ構成図を書くときに便利な記号
https://qiita.com/paty-fakename/items/c82ed27b4070feeceff6
Markdown記法 サンプル集
https://qiita.com/tbpgr/items/989c6badefff69377da7
Qiitaのテーブルの書き方についてまとめた
https://qiita.com/zakuroishikuro/items/f33929eab9d55c5bd073
- 投稿日:2021-02-26T15:36:37+09:00
[Rails] deviseを日本語化する方法
前提
Railsにdeviseのgemを導入していること。
日本語化する方法
Gemfileに追記する
gem 'devise'
の後に以下を追記して、bundle install
します。Gemfilegem 'devise-i18n'devise.rbを編集する
config/initializers/devise.rbconfig.scoped_views = trueそして
rails s
でサーバーを再起動します。devise.views.ja.yml を作成する
以下を実行すると
config/locales
に日本語化ファイル(devise.views.ja.yml
)が作成されます。$ rails g devise:i18n:locale ja
- 投稿日:2021-02-26T15:10:23+09:00
Rails・Laravel・Docker・React環境構築一覧
本記事は、TechpitがQiitaで投稿している環境構築記事のまとめです。
今後も適時更新していきます(最終更新日:2021/2/26)
「〇〇の環境構築も掲載してほしい!」
「〇〇のエラーが解決できない」
等ありましたら、ぜひコメントしてください。記事一覧表
Ruby on Rails
0から Ruby on Rails の環境構築【macOS】 (Homebrew のインストールから Rails のインストールまで)
0からRuby on Railsの環境構築【Cloud9】(Rubyのバージョン変更からRailsのインストールまで)
Laravel
【最新版】はじめてのLaravel!Laradockを用いたLaravel開発環境構築
React
React環境構築【Node.jsのインストールからCreate React Appまで】~macOS編~
React環境構築【Node.jsのインストールからCreate React Appまで】~Windows編~
Docker
?【すぐ開発を始めたい人向け】DockerをMacにインストールする手順?
今後もプログラミング学習に役立つような情報を発信していきます!アカウントをフォローしてお待ちください!
- 投稿日:2021-02-26T14:45:33+09:00
interactor gem についてまとめてみた (1/2)
業務で interactor gem1 を使用する機会があったのですが、日本語の情報がそれほど多くなかったためまとめました。
全2部作となっていて、
1. Interactor 概論(今回)
2. ActiveInteractor Gem で Interactor を使いやすく
という構成でお届けします。
今回は、 interactor gem の README を翻訳したもの +α の内容となっています。Interactor とは
Interactor とは、アプリケーションのビジネスロジックをカプセル化するために使われるオブジェクトです。それぞれの Interactor はアプリケーションが行う1つの動作を表現します。
ビジネスロジックとは
ビジネスロジックとは、アプリケーションにおいてデータがどのように作られ、保存され、変更されるか( = ビジネスルール)を記述したソースコードのことです。データベースそのものの管理やUIの表示、システム構成やプログラムの様々な部分の連動などとは区別されます。
Context
それぞれの Interactor は、自身が動作するために必要な情報を集めたオブジェクトである Context を持っています。Interactor が動作すると、その Interactor が持つ Context が変更されます。
Context に情報を追加する
context.user = userこのように記述すると、 Context にユーザーの情報を追加することができます。
Context を落とす
Interactor の動作に問題が生じた場合、以下のようにして Context を落とすことができます。
context.fail!以下のようにしてエラーメッセージを追加することもできます。
context.error = "Boom!" context.fail! # または context.fail!(error: "Boom!")Context が落ちているかどうかを確認することもできます。
context.failure? # => false context.fail! context.failure? # => true # または context.success? # => true context.fail! context.success? # => false落ちた Context を処理する
context.fail!
が実行されると常に例外タイプInteractor::Failure
が返りますが、この例外を認識することは通常ありません。後に述べるように、コントローラからcall
クラスメソッドを用いて Interactor を呼び出し、その後context.success?
メソッドを使ってチェックする方法が推奨されます。call
クラスメソッドはfail!
が実行されても例外を発生させることはありません。
ただし、 Interactor の単体テストにおいてcall
ではないビジネスロジックメソッドを呼び出している際は、fail!
メソッドはInteractor::Failure
を返すことに注意してください。フック
before
フックInteractor が実行される前に Context の準備が必要な場合は、
before
フックを使用することができます。引数にはブロックかシンボルを指定することができます。before do context.emails_sent = 0 end # または before :zero_emails_sent def zero_emails_sent context.emails_sent = 0 end
after
フックInteractor が実行された後の動作を指定することもできます。
after do context.user.reload endただし、
after
フックは Interactor が正常終了した場合にのみ実行されます。fail!
が実行された場合は、after
フックは実行されません。
around
フック
before
フックやafter
フックと同様にaround
フックを定義することもできます。around
ブロックやこれに使われるメソッドは1つの引数を受け取り、その引数のcall
メソッドを呼び出すことで Interactor を呼び出します。around do |interactor| context.start_time = Time.now interactor.call context.finish_time = Time.now end # または around :time_execution def time_execution(interactor) context.start_time = Time.now interactor.call context.finish_time = Time.now end
fail!
メソッドが呼び出された場合、around フックの動作が止まり、interactor.call
の後に記述された処理は実行されません。フックの実行順
before
フックは定義された順番に、after
フックは定義された順番と逆に実行されます。around
フックはbefore
フックやafter
フックの外側で実行されます。
例えば、around do |interactor| puts "around before 1" interactor.call puts "around after 1" end around do |interactor| puts "around before 2" interactor.call puts "around after 2" end before do puts "before 1" end before do puts "before 2" end after do puts "after 1" end after do puts "after 2" endこのように定義された場合、実行結果は以下のようになります。
around before 1 around before 2 before 1 before 2 after 2 after 1 around after 2 around after 1Interactor の Concern
Interactor は以下のようにして、 Concern の中で共通に用いられる複数の before/after フックを定義することができます。
module InteractorTimer extend ActiveSupport::Concern included do around do |interactor| context.start_time = Time.now interactor.call context.finish_time = Time.now end end endInteractor の例
ユーザーの認証を行う場合、次のようにして Interactor を用いることができます。
class AuthenticateUser include Interactor def call if user = User.authenticate(context.email, context.password) context.user = user context.token = user.secret_token else context.fail!(message: "authenticate_user.failure") end end end
Interactor
モジュールを include したクラスを作り、その中でcall
インスタンスメソッドを作成するだけで Interactor を定義することができます。 Interactor は、call
メソッドの中で自身のcontext
にアクセスすることができます。コントローラ内での Interactor
多くの場合、 Interactor はコントローラの中で用いられます。
class SessionsController < ApplicationController def create if user = User.authenticate(session_params[:email], session_params[:password]) session[:user_token] = user.secret_token redirect_to user else flash.now[:message] = "Please try again." render :new end end private def session_params params.require(:session).permit(:email, :password) end endこのようなコントローラは、先ほどの
AuthenticateUser
クラスを用いて次のようにリファクタリングされます。class SessionsController < ApplicationController def create result = AuthenticateUser.call(session_params) if result.success? session[:user_token] = result.token redirect_to result.user else flash.now[:message] = t(result.message) render :new end end private def session_params params.require(:session).permit(:email, :password) end end
call
クラスメソッドを用いることで、 Interactor を動作させることができます。call
に引数として渡したハッシュは Interactor のインスタンスであるcontext
に変換され、該当の Interactor 内で定義されたフックと共に実行され、最終的に変更が加えられた Context が返ります。Interactor の利点
ユーザーの認証を行う先ほどの例では、コントローラは以下のようになりました。
class SessionsController < ApplicationController def create result = AuthenticateUser.call(session_params) if result.success? session[:user_token] = result.token redirect_to result.user else flash.now[:message] = t(result.message) render :new end end private def session_params params.require(:session).permit(:email, :password) end endこのような単純なユースケースでは、 Interactor を使った方がコードの分量は多くなります。ではなぜ Interactor を使うのでしょうか?
動作が明快になる
Interactor は全ての破壊的変更(
POST
やPUT
、DELETE
リクエスト)を伴う動作に対して使います。 Interactor はapp/interactors
ディレクトリに作成されるため、このディレクトリを見れば全ての開発者がアプリケーションの行う動作を理解することができます。▾ app/ ▸ controllers/ ▸ helpers/ ▾ interactors/ authenticate_user.rb cancel_account.rb publish_post.rb register_user.rb remove_post.rb ▸ mailers/ ▸ models/ ▸ views/TIP: Interactor は実装ではなく、ビジネスロジックに由来した名前にしましょう。アカウントの削除を行う Interactor を作成する際に
DestroyUser
ではなくCancelAccount
という名前にすることで、将来的に様々な責任を担うことができるようになります。将来性2
今回のユーザー認証の例では、ユーザーを認証するというシンプルなタスクであっても、次のように複数の責任を担うことになるでしょう。
- 久しぶりにログインしたユーザーに対して「おかえりなさい」と言う
- パスワードを更新するように通知する
- 何度もログインが失敗するユーザーを凍結する
- 凍結した旨をメールで通知する
リストが増えていけば行くほど、コントローラの分量も増えていきます。これで長大なコントローラの出来上がりです。
その代わりに Interactor を導入することで、担うべき責任が増えたとしても、コントローラ(とそのテスト)にはほとんど(あるいは全く)変更を加える必要がなくなります。追加される責任に応じて適切に変更すべき Interactor を選ぶことで、追加される動作が変更されることを防ぐことができます。Interactor の種類
Interactor ライブラリの中には、普通の Interactor と Organizer という2つの種類の Interactor が定義されています。
Interactor
普通の Interactor は
Interactor
を include し、call
メソッドを定義したクラスのことを指します。class AuthenticateUser include Interactor def call if user = User.authenticate(context.email, context.password) context.user = user context.token = user.secret_token else context.fail!(message: "authenticate_user.failure") end end end通常の Interactor はさしずめ建築用のブロックのようなもので、アプリケーションにおいて単一の働きをします。
Organizer
Organizer は Interactor の変種として重要な意味を持ちます。 Organizer は他の Interactor を動作させるためだけに用いられます。
class PlaceOrder include Interactor::Organizer organize CreateOrder, ChargeCard, SendThankYou end
PlaceOrder
Organizer は、コントローラ内で他の Interactor と同様に用いることができます。class OrdersController < ApplicationController def create result = PlaceOrder.call(order_params: order_params) if result.success? redirect_to result.order else @order = result.order render :new end end private def order_params params.require(:order).permit! end endOrganizer は、自身が organize する Interactor をまとめて順序通り実行します。 Organizer の中の Interactor は、動作して Context を変更した状態で次の Interactor に手渡します。
ロールバック
organize された Interactor のうちどれかの動作が失敗した場合、 Organizer は動作を停止します。
ChangeCard
Interactor が失敗した場合、SendThankYou
は呼び出されません。
加えて、すでに実行された Interactor は自身の行った動作を巻き戻すことができます。rollback
メソッドを Interactor に定義し、呼び出すだけで実現できます。class CreateOrder include Interactor def call order = Order.create(order_params) if order.persisted? context.order = order else context.fail! end end def rollback context.order.destroy end end動作が失敗した Interactor はロールバックされません。全ての Interactor は単一の目的を持っているはずなので、仮にその動作が失敗したとしても、その動作を巻き戻す必要はないと考えられるからです。
Interactor のテスト
適切に書かれていれば、それぞれの Interactor は1つのことを行うだけなので、 Interactor をテストするのは簡単です。
class AuthenticateUser include Interactor def call if user = User.authenticate(context.email, context.password) context.user = user context.token = user.secret_token else context.fail!(message: "authenticate_user.failure") end end endテストする必要があるのは、この Interactor が行う単一の動作と、 Context に与える影響のみです。
describe AuthenticateUser do subject(:context) { AuthenticateUser.call(email: "john@example.com", password: "secret") } describe ".call" do context "when given valid credentials" do let(:user) { double(:user, secret_token: "token") } before do allow(User).to receive(:authenticate).with("john@example.com", "secret").and_return(user) end it "succeeds" do expect(context).to be_a_success end it "provides the user" do expect(context.user).to eq(user) end it "provides the user's secret token" do expect(context.token).to eq("token") end end context "when given invalid credentials" do before do allow(User).to receive(:authenticate).with("john@example.com", "secret").and_return(nil) end it "fails" do expect(context).to be_a_failure end it "provides a failure message" do expect(context.message).to be_present end end end end今回は RSpec で記載しましたが、どのテストフレームワークを用いても考え方は同じです。
分離
上のテストにおいて、ユーザーをデータベースに作成せずに
User.authenticate
をスタブ化したのは、spec/interactors/authenticate_user_spec.rb
の目的がAuthenticateUser
Interactor をテストすることだけだったからです。User.authenticate
メソッドはspec/models/user_spec.rb
でテストされます。モデルに独自のインターフェースを定義するのは賢いやり方です。このようにすることで、 Interactor に属するある責任がどのモデルと関連づけられるのかがわかりやすくなり、コードを記述しやすくなります。
User.authenticate
メソッドは良い例です。もしこの Interactor がclass AuthenticateUser include Interactor def call user = User.where(email: context.email).first # ユーザーを認証する処理が直接記述されている if user && BCrypt::Password.new(user.password_digest) == context.password context.user = user else context.fail!(message: "authenticate_user.failure") end end endこのようになっていた場合、この Interactor をテストするのはとても難しくなります。仮にテストできたとしても、共にモデルの関心事であるORMや暗号化アルゴリズムを変更した瞬間に、ビジネスロジックの関心事であるこの Interactor は壊れてしまいます。
1行1行の目的を明確にしましょう。統合
Interactor のテストを分離することが大事である一方で、統合テストを適切に行うことも重要です。
あるメソッドをスタブ化すると、そのメソッドが壊れたり変更されたり、もしくはそもそも存在しなかったりする場合を隠蔽することができてしまいます。
統合テストを書けば、アプリケーションの個々のパーツが期待通り動くかどうかを確かめることができ、これは Interactor のような新しいレイヤーをプログラムに追加する場合に特に重要です。
TIP: テストカバレッジを見るのであれば、統合テストを書く前に100%のカバレッジを目指しましょう。それから統合テストを書けば、あなたは夜も安心して寝ることができます。コントローラ
Interactor を用いる利点のひとつは、コントローラやそのテストを完結にすることができることです。 Interactor は単体テストと統合テストでテストされているので(ですよね?)、コントローラのテストからビジネスロジックのテストを省くことができます。
class SessionsController < ApplicationController def create result = AuthenticateUser.call(session_params) if result.success? session[:user_token] = result.token redirect_to result.user else flash.now[:message] = t(result.message) render :new end end private def session_params params.require(:session).permit(:email, :password) end enddescribe SessionsController do describe "#create" do before do expect(AuthenticateUser).to receive(:call).once.with(email: "john@doe.com", password: "secret").and_return(context) end context "when successful" do let(:user) { double(:user, id: 1) } let(:context) { double(:context, success?: true, user: user, token: "token") } it "saves the user's secret token in the session" do expect { post :create, session: { email: "john@doe.com", password: "secret" } }.to change { session[:user_token] }.from(nil).to("token") end it "redirects to the homepage" do response = post :create, session: { email: "john@doe.com", password: "secret" } expect(response).to redirect_to(user_path(user)) end end context "when unsuccessful" do let(:context) { double(:context, success?: false, message: "message") } it "sets a flash message" do expect { post :create, session: { email: "john@doe.com", password: "secret" } }.to change { flash[:message] }.from(nil).to(I18n.translate("message")) end it "renders the login form" do response = post :create, session: { email: "john@doe.com", password: "secret" } expect(response).to render_template(:new) end end end end全ての魔法は Interactor の中で起きているので、このコントローラのテストは、アプリケーションが運用され続ける限り小さな変更を加え続けなければいけないでしょう。
Rails
Interactor を Rails で使うときは、 Interactor は
app/interactors
に格納し、次のように動詞となる名前をつけています。
AddProductToCart
AuthenticateUser
PlaceOrder
RegisterUser
RemoveProductFromCart
参照: interactor-rails
Copyright (c) 2013 Collective Idea, Released under the MIT license: https://github.com/collectiveidea/interactor/blob/master/LICENSE.txt ↩
ネタバレになりますが、あなたのユースケースはこれほどシンプルにはならないはずです。 ↩
- 投稿日:2021-02-26T14:32:21+09:00
7つのアクション以外にViewを増やす方法
はじめに
オリジナルアプリケーションを作成中に、疑問に思いました。
「7つのアクション以外にviewって作れるのか?」
初心者なら必ずこう思うはず、
そして、作れないことはないはず!!
調べてみたら作れました!!忘れないために記事に残したいと思います。
やることは、以下の3つになります。
1.コントローラーへの記述
2.ルーティングの設定
3.ビューの作成順を追って説明します。
バージョン
・Ruby 2.6.5
・Rails 6.0.0コントローラーの設定
まずは、コントローラーに新しいアクションを設定します。
def index @post = Post.includes(:user).order("created_at DESC") @post_like = Post.includes(:post_likes).sort {|a,b| b.post_likes.size <=> a.post_likes.size} end def new @post = Post.new end def create @post = Post.new(post_params) if @post.save redirect_to root_path else render :new end end def show @comment = Comment.new @comments = @post.comments.includes(:user) @comment_like = @post.comments.includes(:likes).sort {|a,b| b.likes.size <=> a.likes.size} end def edit end def update if @post.update(post_params) redirect_to post_path else render :edit end end def destroy if @post.destroy redirect_to root_path end end def indexabout end # 以下省略今回は、アプリケーションの概要説明みたいなページを作成しました。
1番下に記述されている、indexaboutが新しく設定したアクションです。
名前はなんでも大丈夫です。
説明が書いてあるだけのページなので、処理も書いていません。ルーティングの設定
次にルーティングを設定します。
root to: 'posts#index' resources :users, only: [:show, :edit, :update, :destroy] resources :posts do collection do get 'indexabout' end # 以下省略7つのアクション以外のルーティングを設定するには、collectionかmember
これを使用すると、生成されるルーティングのURLと実行されるコントローラーを
任意にカスタムできます。collectionとmemberの違いは?
collection→ルーティングにidがつかない
member→ルーティングにidがつくこの違いです。
例えば、ユーザー情報の編集ページには、idが必要ですよね。
そういったページを作成する場合は、memberを使います。
今回は、説明だけのページなのでcollectionを使いました。ルーティングに記述を行った後にターミナルで確認すると
% rails routesしっかりと設定されているのがわかります。
ビューの設定
最後にビューを設定します。
<div class="what_index"> <%= link_to "正解のない問題たちとは?", indexabout_posts_path, class: :nav__btn %> </div>リンク先の指定をしてあげる。
該当箇所にビューの設定を行い、
作成したビューに記述すれば完了です。これで、7つのアクション以外にもビューが作成できました。
初心者のため、まだまだ誤った記述があるかもしれません。
見つけた方はコメントを残していただければ幸いです。
- 投稿日:2021-02-26T14:25:54+09:00
RailsのSQLインジェクション対策まとめ
find、find_by
普通に使っている分にはSQLインジェクションは発生しないのであまり気にする必要なし。
(参考)
ActiveRecord::Base#find is SQL injection free? - Rails - Ruby-Forumシンプルなwhere句
# NG User.where("name='#{params[:name]}'") # OK User.where(name: params[:name]) User.where("name = ?", params[:name])複数条件で検索
# NG User.where("name = #{params[:name]} or age < #{params[:age]}") # OK User.where("name = ? or age < ?", params[:name], params[:age])Likeを使ったあいまい検索
# NG User.where('name LIKE ?', "%#{params[:name]}%") # OK User.where('name LIKE ?', "%#{sanitize_sql_like(params[:name])}%")参考
- 投稿日:2021-02-26T14:15:48+09:00
【rails】モデルに定義するメソッドのselfについて
モデルにメソッドを書く際に、”self.メソッド名”と書くことがあるかと思います。
まだ完全には理解できていませんが、わかった内容を記事にまとめます。モデル
cook.rbclass Cook < ApplicationRecord def self.ranks Cook.find(Like.group(:cook_id).order(Arel.sql('count(cook_id) desc')).limit(9).pluck(:cook_id)) end endとある記事を参考にして、いいねのランキング機能を実装しました。
ですが、このコード省略は以下のように省略できます。cook.rbclass Cook < ApplicationRecord def self.ranks find(Like.group(:cook_id).order(Arel.sql('count(cook_id) desc')).limit(9).pluck(:cook_id)) end end何が変わったのかというと、3行目の'Cook'を外しました。
なぜ省略できるのか
今回の記事のポイントはここです。まず、2行目のselfについて。
selfとは"自分自身"という意味なので、今回の場合、selfはCookを指します。(ここでのCookは1行目class CookのCookです)
説明文書的に表現すると、「selfの実行主体はCookである」ということです。
そして3行目のCook.find~の「findの実行主体もCook」です。
つまり、"self.メソッド名"と"self.メソッド名の中で実行しているメソッド"の実行主体が同じであれば、省略可能
となります。
最後に
そもそもほとんど記事のコピペでランキング機能を実装してしまったのがダメでしたね。コピペでコードを使うのは良くないことを実感しました笑
また、selfが表しているものについては何となく理解できてきた気はしますが、どんなときにselfを使うのかはボンヤリしています。。。以上です!ありがとうございました!
- 投稿日:2021-02-26T14:12:59+09:00
Railsアプリの標準言語を日本語に設定する
環境
macOS Big Sur Ver11.2.1
Rails6.0.0
Ruby2.6.5目的
Railsで開発中のアプリケーションの標準言語を日本語に変更すること。
これにより、エラーメッセージが日本語表示されること。
※今回のアプリではdeviseを導入しているので、deviseによるデフォルトのメッセージも日本語化します!手順
①アプリケーション自体の言語設定を日本語にする
以下の一文を追加
config/application.rbrequire_relative 'boot' require 'rails/all' Bundler.require(*Rails.groups) module アプリ名 class Application < Rails::Application config.load_defaults 6.0 # 日本語の言語設定を追記する config.i18n.default_locale = :ja end end②必要なGemのインストール
Gemfileに以下のgemを追記して、ターミナルでbundle installする。
すべての環境に適用されるよう、最下部とかに記述する。
参考:rails-i18nGemfile# アプリケーションの日本語対応用gem gem 'rails-i18n'ここまででアプリの設定は完了!
③deviseの言語設定
上記までの作業である程度の日本語化は完了しますが、deviseを導入してユーザー管理機能を構築しているので、deviseに関係するエラーメッセージはまだ英語表記のはず。
そこで、以下のファイルを作成し、config/locales 内に配置する。
ファイルの中身は、コピペでOK
devise-i18n
これでdeviseに関連する部分はすべて日本語化されます。④独自に追加したカラムなどの日本語化
deviseを導入していてもusersテーブルに独自にカラムを追加している場合は、これまでの作業を実施しても独自カラムの表記は英語のままです。
おそらくほとんどの場合は上記までの作業では不十分なはず。
ここからは、自分で設定用ファイルを作って、設定も自分で記述していく必要がある。ということで、config/localesに、「ja.yml」というファイルを作成し、その中に自分で設定を記述していく!
例えばこんな感じ。ja: activerecord: attributes: user: nickname: ニックネーム birthday: 生年月日 item: name: 商品名 explain: 商品の説明 # フォームオブジェクトを用いている場合 activemodel: attributes: order_shipping: postal_code: 郵便番号 prefecture_id: 都道府県 token: カード情報attributesは属性、userはモデルのファイル名(フォームオブジェクトならクラスのファイル名)、nicknameはモデルやクラスで定義している属性名といった感じでしょうか。
モデルやクラスの継承先に気をつけて記述をしてください!
記述したらサーバーを再起動して、エラーメッセージが全て日本語化できているか確認してください。以上、Railsアプリケーションの日本語化の手順でした。
- 投稿日:2021-02-26T14:03:22+09:00
[memo]Heroku利用方法
Herokuを使ってアプリケーションをデプロイする方法をまとめます。
[手順]
●はじめてデプロイをする場合
・Herokuにアカウント登録する
・Heroku CLIをインストールする
・masterブランチへcommitする
・Heroku上にアプリケーションを作成する
・MySQLを使用できるように設定する
・master.keyを環境変数として設定する
・Herokuへアプリケーションの情報をpushする
・Heroku上でマイグレーションを実行する●デプロイ済みのアプリケーションに変更修正を加えた場合
・変更修正をcommitする
・ブランチを作成していた場合は、masterブランチへマージする
・Heroku上にpushする
(テーブルに変更を加えた場合は)Heroku上でマイグレーションを実行するHerokuの導入
Heroku CLIをインストール
# ターミナル % brew tap heroku/brew && brew install heroku # 完了確認コマンド % heroku --version # バージョンが出力されれば成功 heroku/7.40.0 darwin-x64 node-v12.16.2インストールについての公式ドキュメント
https://devcenter.heroku.com/articles/heroku-cliHerokuにログイン
% heroku login --interactive => Enter your Heroku credentials. # メールアドレスを入力し、エンターキーを押す => Email: # パスワードを入力して、エンターキーを押す => Password:Heroku上にアプリケーションを作成
アプリ作成公式ドキュメント
https://devcenter.heroku.com/articles/getting-started-with-rails6# 作成したいアプリのディレクトリへ移動し以下コマンド実行 % heroku create アプリ名アプリ名はアンダーバー
_
ではなくハイフン-
を使う
例:meisai-app 等Heroku上でMySQLを使えるようにする
# ClearDBアドオンを追加 % heroku addons:add cleardb # 下記のように出力されれば成功 Creating cleardb on ⬢ meisai-app... free Created cleardb-vertical-00000 as CLEARDB_DATABASE_URL Use heroku addons:docs cleardb to view documentation # 設定を変更 # ClearDBデータベースのURLを変数heroku_cleardbに格納 % heroku_cleardb=`heroku config:get CLEARDB_DATABASE_URL` # データベースのURLを再設定 % heroku config:set DATABASE_URL=mysql2${heroku_cleardb:5} →mysql2というGemを使用しているので、DATABASE_URLの冒頭がmysql2://に変更されているHeroku上で非公開の値を管理する
credentials.yml.encファイル
Railsにて、外部に漏らしたくない情報を扱う際に用いるファイル。
通常時は、英数字の文字列で構成された暗号文が表示され、ファイル内に何が書かれているのか分からないようになっています。このcredentials.yml.encと対になるmaster.keyが存在する場合、credentials.yml.encの暗号文を復号し、ファイル内の記述を確認できます。master.keyファイル
credentials.yml.encの暗号文を復号する、鍵の役割を持ったファイルです。特定のcredentials.yml.encと対になっているため、その他のcredentials.yml.encへは、効果を発揮しません。
master.keyはデフォルトで.gitignoreに記述されており、Gitで管理されない仕組みになっています。credentials.yml.encの中身を確認
アプリのディレクトリを開き、config/credentials.yml.encを開くと暗号文が確認できる
credentials.yml.encをmaster.keyによって復号
# ターミナル % EDITOR="vi" bin/rails credentials:edit # 確認後は、「escキー」→「:」→「q」と入力し、「enterキー」を押して credentials.yml.encを閉じるHeroku上にmaster.keyを設置
Herokuへアプリケーションのコードをデプロイします。
しかし、その際にデプロイされるコードというのは、Gitで管理されているコードになります。
つまり、master.keyはこのままだとHeroku上へデプロイできず、credentials.yml.encもHeroku上で扱えないということになります。そこで、Heroku上に別途master.keyを設置し、Heroku上でもcredentials.yml.encを扱えるようにする必要があります。
環境変数
という「どのディレクトリ・ファイルからでも参照できる変数」を使いmaster.keyの値を設置します。
heroku configコマンドの使用
Heroku上で環境変数の参照・追加・削除等をする場合に用います。環境変数の追加であればheroku config:set 環境変数名="値"と実行します。そうすることによって、Heroku上で環境変数を追加できます。Heroku上で環境変数を設定
# Heroku上に環境変数を設定 % heroku config:set RAILS_MASTER_KEY=`cat config/master.key` # Heroku上で環境変数を確認 % heroku config # RAILS_MASTER_KEYという変数名で値が設定されていれば成功アプリケーションをプッシュ
# Rubyのバージョン2.6.5が動作するStack(動作環境)を指定 % heroku stack:set heroku-18 -a アプリ名 # アプリケーションをHerokuへ追加 % git push heroku masterエラーが出る場合
git push heroku master 実行時に「remote: ! Could not detect rake tasks」「remote: ! ensure you can run $ bundle exec rake -P against your app」とエラーが表示される場合は、bundlerのバージョンがエラーの原因である可能性が高いので次の手順を試す。
# 現在入っているbundlerを削除[何度か確認を求められますが、「y」を入力してエンター] % gem uninstall bundler # bundlerのバージョン2.1.4を指定してインストール % gem install bundler -v '2.1.4' # ディレクトリ内の Gemfile.lock を削除 # Gemfile.lockを作り直す % bundle install # 変更をGitHubへ反映 GitHubDesktopより、commit と push # Herokuにアプリケーションの情報を追加 % git push heroku masterHerokuデータベースにマイグレーションの情報を反映
# Heroku上でマイグレーションを実行 % heroku run rails db:migrate公開を確認
# Herokuにデプロイされたアプリケーションの情報を確認 % heroku apps:info ===meisai-app Addons: cleardb:ignite Auto Cert Mgmt: false Dynos: web: 1 Git URL: https://git.heroku.com/meisai-app.git Owner: sample@sample.com Region: us Repo Size: 165 KB Slug Size: 56 MB Stack: heroku-18 Web URL: https:/meisai-app.herokuapp.com/エラーが出る場合のログの確認
# ログの最後の10行を表示するためのtailオプションを使いログ表示 % heroku logs --tail --app アプリ名 # 出力結果 2020-05-08T09:03:30.572301+00:00 app[web.1]: F, [2020-05-08T09:03:30.572206 #4] FATAL -- : [52bf1bef-ea70-4df0-897a-6d0c3d925b1e] 2020-05-08T09:03:30.572302+00:00 app[web.1]: [52bf1bef-ea70-4df0-897a-6d0c3d925b1e] ActionView::Template::Error (undefined method `checked' for #<Post:0x000055f0ec57ec88>): 2020-05-08T09:03:30.572303+00:00 app[web.1]: [52bf1bef-ea70-4df0-897a-6d0c3d925b1e] 7: <div id="list"> 2020-05-08T09:03:30.572303+00:00 app[web.1]: [52bf1bef-ea70-4df0-897a-6d0c3d925b1e] 8: </div> 2020-05-08T09:03:30.572304+00:00 app[web.1]: [52bf1bef-ea70-4df0-897a-6d0c3d925b1e] 9: <% @posts.each do |post| %> 2020-05-08T09:03:30.572305+00:00 app[web.1]: [52bf1bef-ea70-4df0-897a-6d0c3d925b1e] 10: <div class="post" data-id = <%= post.id %> data-check=<%= post.checked %>> 2020-05-08T09:03:30.572305+00:00 app[web.1]: [52bf1bef-ea70-4df0-897a-6d0c3d925b1e] 11: <div class="post-date"> 2020-05-08T09:03:30.572306+00:00 app[web.1]: [52bf1bef-ea70-4df0-897a-6d0c3d925b1e] 12: 投稿日時:<%= post.created_at %> 2020-05-08T09:03:30.572306+00:00 app[web.1]: [52bf1bef-ea70-4df0-897a-6d0c3d925b1e] 13: </div> 2020-05-08T09:03:30.572306+00:00 app[web.1]: [52bf1bef-ea70-4df0-897a-6d0c3d925b1e] 2020-05-08T09:03:30.572307+00:00 app[web.1]: [52bf1bef-ea70-4df0-897a-6d0c3d925b1e] app/views/posts/index.html.erb:10 2020-05-08T09:03:30.572307+00:00 app[web.1]: [52bf1bef-ea70-4df0-897a-6d0c3d925b1e] app/views/posts/index.html.erb:9 2020-05-08T09:03:30.574057+00:00 heroku[router]: at=info method=GET path="/" host=ajax-app-123456.herokuapp.com request_id=52bf1bef-ea70-4df0-897a-6d0c3d925b1e fwd="125.12.120.68" dyno=web.1 connect=1ms service=79ms status=500 bytes=1827 protocol=https 2020-05-08T09:03:31.014022+00:00 heroku[router]: at=info method=GET path="/favicon.ico" host=ajax-app-123456.herokuapp.com request_id=56eb5531-4288-48d7-9614-b58fed6deb86 fwd="125.12.120.68" dyno=web.1 connect=2ms service=2ms status=304 bytes=48 protocol=httpsデプロイ済みのアプリケーションに変更を加えた場合
ファイルの変更履歴が存在する場合
# Githubのmasterブランチへcommitする(Githubデスクトップからでもok) % git add . % git commit -m "後から見てわかりやすいコミット名" # 作成したコミットをHerokuへプッシュ % git push heroku masterファイルの変更履歴が存在しない場合
Herokuの仕様上、最新のコミット履歴が存在しない状態でgit push heroku masterコマンドを実行すると「Everything up-to-date(すでに最新の状態に更新されています)」と表示されます。その場合は空のコミットを作成してHerokuにプッシュする方法を使います。
# 空のコミットを生成 % git commit --allow-empty -m "空のcommit" # 作成したコミットをHerokuへプッシュ % git push heroku master
- 投稿日:2021-02-26T12:53:42+09:00
学び直し Rubyがミニツク Part9
今日の教科書
モジュールモジュール
モジュールは手続きの部分だけのまとめ。
moduleでモジュールを定義。module <モジュール名> <モジュールの定義> endmodule Foo def foo puts("module foo") end endクラスとモジュールの違いは
- モジュールはインスタンスを作ることができない
- モジュールは継承ができないクラスは継承をおこなうことでスーパークラスから機能を渡せました。
しかし、クラスの継承ではスーパークラスがひとつしか指定できません。
そのため、複数のスーパークラスを継承して新しいサブクラスを作れません。そのようなクラスに異なる機能を渡したい時にRubyではモジュールを使います。このさまざまな機能を混ぜ合わせるやり方のことをMix-inと呼びます。include
モジュールに含まれているメソッドや定数をクラスの中に取り込む。
module Greeting def hello puts("Hello, Ruby!") end end class Foo include Greeting end class Bar include Greeting end Foo.hello #=> Hello, Ruby! Bar.hello #=> Hello, Ruby!同じクラスへ複数のモジュールをインクルード
module Foo def foo puts("foo") end end module Bar def bar puts("bar") end end class Baz include Foo include Bar end baz = Baz.new baz.foo #=> foo baz.bar #=> bar別のモジュールに機能を渡す
module Foo
def foo
puts("foo")
end
end
module Bar
include Foo
end
class Baz
include Bar
end
baz = Baz.new
baz.foo #=> foo
モジュール関数
module Foo def foo puts("foo") end module_function :foo end Foo.foo #=> fooモジュールで定義したインスタンスメソッドはレシーバを指定した定式では呼べない。
レシーバにモジュールを指定してメソッド呼び出しを行う。モジュール内でmodule_functionの引数にメソッド名をシンボルで指定することで設定できる。このようなメソッドをモジュール関数と呼ぶ。名前空間の提供
別の人が開発していたライブラリを使ったり、複数のメンバーで開発をおこなっていると使いたい名前が被ってしまうことがあります。
ただし、同じ名前のクラスやメソッドを定義すると、前に定義していたものを上書きしてしまいます。上書きする前に使っていたものと違う機能になってしまうと、上書きする前の機能を使っていたところでエラーが発生するかもしれません。このような名前が衝突することによって生まれる問題を避け、自由に名前を付けることができることを名前空間と呼びます。Rubyではモジュールを使うことによって、名前空間を提供することができます。module Foo def foo puts("module foo") end module_function :foo end module Bar def foo puts("module bar") end module_function :foo end Foo.foo #=> module foo Bar.foo #=> module bar特異メソッド
特定の一つのオブジェクトだけで使えるメソッド。
メソッド定義時にオブジェクト.メソッド名
で使える。obj = Object.new def obj.foo puts("foo") endクラスやモジュールも特異メソッドを定義できる。クラスメソッドも特異メソッドの一種。
クラスメソッドはクラスをレシーバにして呼び出す。# def self.メソッド名; end module Foo def self.foo puts("foo") end end Foo.foo #=> foo # def モジュール.メソッド名; end module Bar def Bar.bar puts("bar") end end Bar.bar #=> barモジュールには似たようなモジュール関数がある。両方ともレシーバにモジュールを指定する。
モジュール関数ではインクルードした際のインスタンスメソッドとして。
特異メソッドではインクルードしたときに渡さない。module Foo def self.foo puts("foo") end end module Bar include Foo end Foo.foo #=> foo Bar.foo NoMethodError: undefined method `foo' for Bar:Module from (irb):9 from :0モジュール関数はインクルードすることによって、インスタンスメソッドとしてインクルード先で使うことができる。
module Foo def foo puts("foo") end module_function :foo end class Bar include Foo def bar foo end end bar = Bar.new bar.foo #=> fooextend
オブジェクトに対して引数に指定したモジュールのインスタンスメソッドを特異メソッドとして渡す。
module Foo def foo puts("foo") end end class Bar end str = "" str.extend(Foo) str.foo #=> foo Bar.extend(Foo) Bar.foo #=> foo定義しているクラスやモジュールの中でも呼び出せる。
module Foo def foo puts("foo") end end module Bar extend Foo end Bar.foo #=> foo組み込みモジュール
Rubyに最初から組み込まれているモジュール。
Comparableモジュール
比較演算子をクラスに加えるモジュールです。ArrayやStringなどの大小関係があるオブジェクトはこのモジュールをインクルードしている。使用時には<=>演算子を定義する。
この演算子を使って比較するメソッドの集まりがComparableモジュール。class Foo include Comparable attr_accessor :num def initialize(num) @num = num end def <=>(other) return @num <=> other.num end end foo = Foo.new(10) bar = Foo.new(5) p foo < bar p foo > barEnumerableモジュール
繰り返しを行うクラスのためのモジュール。eachメソッドを必要とする。
これで様々なイテレータを定義できる。class MetaSyntax include Enumerable def initialize @variables = [] end def add(value) @variables << value end def each @variables.each do |variable| yield variable end end end ary = MetaSyntax.new ary.add("foo") ary.add("bar") ary.add("baz") ary.each do |i| puts i end ary.each_with_index do |item, index| puts("これは#{index}番目の#{item}です") endクラス内にeachを定義すると、Enumerableモジュールをインクルードする。
そしてeach_with_indexメソッドといったメソッドが使えるようになる。
- 投稿日:2021-02-26T12:23:53+09:00
チャット機能追加
相手を指定してチャットルームを作成し、会話ができる機能を追加しましょう。
まずどのような機能とテーブルが必要か考えてみます。
チャットルーム管理機能(新規作成・削除機能) roomsテーブル
メッセージ管理機能(テキスト投稿・画像投稿機能) messagesテーブルroomsテーブル
チャットルームの名前
messagesテーブル
チャットの内容
画像情報
メッセージの投稿時刻
投稿したチャットルーム
投稿したユーザーテーブル間の関係性
usersテーブル と messagesテーブル
どのユーザーがどの投稿したかを管理する
roomsテーブル と messagesテーブル
どのチャットルームで投稿されたメッセージなのかを管理する
usersテーブルとroomsテーブル
ユーザーがどのチャットルームにいるかを管理
チャットルームにどのユーザーが存在するかを管理アソシエーション
今回のDB設計では「多対多」という関係性が存在します。
関連するテーブルのidをお互いが複数持っている関係性のことで、今回のように「ユーザーは複数のチャットルームに所属する」「チャットルームには複数ユーザーが所属する」という場合の関係性を指します。
この場合は中間テーブルを使用して設計します。中間テーブル
多対多の関係にある2つのテーブルの間に挟まって、2つの組み合わせパターンだけをレコードとして保存します。
中間テーブルを経由して多対多のテーブルへアソシエーションを組むには、has_manyメソッドにthroughオプションを記述する必要があります。
class Room < ApplicationRecord has_many :room_users has_many :users, through: :room_users endclass User < ApplicationRecord has_many :messages has_many :rooms, through: :room_users endclass RoomUser< ApplicationRecord belongs_to :user belongs_to :room endそれでは追加していきます。
ルーティングを追加
Rails.application.routes.draw do #省略 resources :rooms, only: [:new, :index, :create, :destroy] do resources :messages, only: [:index, :create] end endユーザーモデルにアソシエーション追記
Userモデルclass User < ApplicationRecord #省略 has_many :room_users has_many :rooms, through: :room_users has_many :messages endRoomモデルとテーブルを作成
% rails g model room
マイグレーションファイルclass CreateRooms < ActiveRecord::Migration[6.0] def change create_table :rooms do |t| t.string :name, null: false t.timestamps end end end% rails db:migrate
Roomモデルclass Room < ApplicationRecord has_many :room_users, dependent: :destroy has_many :users, through: :room_users has_many :messages, dependent: :destroy validates :name, presence: true endrommsコントローラーを作成
roomsコントローラーclass RoomsController < ApplicationController def new @room = Room.new end def create @room = Room.new(room_params) if @room.save redirect_to rooms_path else render :new end end def index end def destroy room = Room.find(params[:id]) room.destroy redirect_to rooms_path end private def room_params params.require(:room).permit(:name, user_ids: []) end enduser_ids: [ ]という記述があります。このように、配列に対して保存を許可したい場合は、キーに対し[ ]を値として記述します。
rooms/newビュー<div class='chat-room-form'> <h1>新規チャットルーム</h1> <%=form_with model: @room, local: true do |f|%> <div class='chat-room-form__field'> <%= f.label :チャットルーム名, class: 'chat-room-form__label'%> <%= f.text_field :name, class: 'chat__room_name chat-room-form__input', placeholder: 'チャットルーム名を入力してください'%> </div> </div> <label class='chat-room-form__label' for='chat_room_チャットメンバー'>チャットメンバー</label> <select name="room[user_ids][]"> <option value="">チャットするユーザーを選択してください</option> <% User.where.not(id: current_user.id).each do |user| %> <option value=<%= user.id %>><%= user.nickname %></option> <% end %> </select> <input name="room[user_ids][]" type="hidden" value=<%= current_user.id %>> <%= f.submit class: 'chat-room-form__action-btn'%> <% end %> </div>モデル名.where("条件")と記述すると、条件に一致したレコードを配列として取得でき、続けてnotメソッドを追記すると条件に一致したレコード以外の値を配列として取得できます。
type属性にhiddenを指定することで、画面上には表示されないinput要素を作成できます。そしてselect要素と同様にname属性にroom[user_ids][]を指定し、room[user_ids]をキーに対する値を配列で受け取る仕組みです。value属性にはcurrent_user.idを指定しているため、room[user_ids]キーに対して、ログイン中ユーザーのidを配列として格納し、コントローラーへ送信します。
room[user_ids][]でコントローラーに送られるparamsの中身{"room" => {"user_ids" => ["選択したユーザーのid", "現在ログインしているユーザーのid"]}}rooms/indexビュー<div class='wrapper'> <div class='side-bar'> <%= render "messages/side_bar" %> </div> </div>部分テンプレートを使います。
RoomUserモデルを作成
% rails g model room_user
マイグレーションファイルclass CreateRoomUsers < ActiveRecord::Migration[6.0] def change create_table :room_users do |t| t.references :room, foreign_key: true t.references :user, foreign_key: true t.timestamps end end end% rails db:migrate
RoomUserモデルclass RoomUser < ApplicationRecord belongs_to :room belongs_to :user endmessageモデル作成
% rails g model massage
マイグレーションclass CreateMessages < ActiveRecord::Migration[6.0] def change create_table :messages do |t| t.string :content t.references :room, foreign_key: true t.references :user, foreign_key: true t.timestamps end end end% rails db:migrate
Messageモデルclass Message < ApplicationRecord belongs_to :user belongs_to :room has_one_attached :image validates :content, presence: true, unless: :was_attached? def was_attached? image.attached? end endmessagesコントローラーclass MessagesController < ApplicationController def index @message = Message.new @room = Room.find(params[:room_id]) @messages = @room.messages.includes(:user) end def create @room = Room.find(params[:room_id]) @message = @room.messages.new(message_params) if @message.save redirect_to room_messages_path(@room) else @messages = @room.messages.includes(:user) render :index end end private def message_params params.require(:message).permit(:content, :image).merge(user_id: current_user.id) end endmessagesにビューを追加
_main_chat.html.erb<%= @room.name %> <%= link_to "チャットを終了する", room_path(@room), method: :delete %> <%= render partial: 'message', collection: @messages %> <%= form_with model: [@room, @message], class: 'form', local: true do |f| %> <%= f.text_field :content, class: 'form-message', placeholder: 'type a message' %> <label class="form-image"> <span class="image-file">画像</span> <%= f.file_field :image, class: 'hidden' %> </label> <%= f.submit '送信', class: 'form-submit' %> <% end %>_message.html.erb<%= message.user.name %> <%= l message.created_at %> <%= message.content %> <%= image_tag message.image.variant(resize: '500x500'), class: 'message-image' if message.image.attached? %>_side_bar.html.erb<%= link_to current_user.nickname, rooms_path %> <%= link_to "チャットを作成する", new_room_path %> <% current_user.rooms.each do |room| %> <%= link_to room.name, room_messages_path(room) %> <% end %>index.html.erb<div class="wrapper"> <div class="side-bar"> <%= render "side_bar" %> </div> <div class="chat"> <%= render "main_chat" %> </div> </div>form_withの引数に@room, @messageの2つを渡しているのはmessagesのルーティングがroomsにネストされているため。チャットルームとメッセージとの間には親子関係があります。
form_withのmodelオプションに記述した@roomには「指定のチャットルームのレコード情報」、@messageには「Messageモデルのからのインスタンス(Message.new)」をあらかじめセットしておく必要があります。
時刻設定
module アプリ名 class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 6.0 config.i18n.default_locale = :ja #設定 config.time_zone = 'Tokyo' #設定 # 中略config/localesディレクトリに「ja.yml」というファイルを作成し、以下のような設定を記述
config/locales/ja.ymlja: time: formats: default: "%Y/%m/%d %H:%M:%S"l(エル)メソッドは、日付や時刻を表示するRailsのメソッドで、指定した現地時間に対応できます。
以上です。
- 投稿日:2021-02-26T11:52:27+09:00
ActionMailerでdefault_url_optionを使って環境毎にURLを変更する
config/environments/**.rb
に環境毎のホスト名を指定して使いたくて調べた。
以下を設定すれば良いとあったのだけど、
config.action_mailer.default_url_options = { host: 'develop.com' }
設定後、どうすればURLが取れるかわからなかった..
しばらく以下の意味がわからなかったけど..
https://railsguides.jp/action_mailer_basics.html#action-mailer%E3%81%AE%E3%83%93%E3%83%A5%E3%83%BC%E3%81%A7url%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8Broutesの普段pathの所をurlに変更して引数を指定すれば、URLを返してくれる。
こんなroutesの設定があったら
activate GET /registrations/:id/activate(.:format) registrations#activate以下のようにcontroller ,viewで指定すればdefault_url_optionsを反映したURLが取れる
activate_url(@user.activation_token)ふいー
- 投稿日:2021-02-26T11:40:10+09:00
Railsコマンドまとめ
よく使うRailsコマンドのメモです。
今まで何となく使っていたのですが、いい機会なので公式ドキュメントから調べてみました。参考:Railsドキュメント
新しいRailsアプリを作成
# 基本形 $ rails new アプリケーション名 [オプション] # オプション何もなしで作成 $ rails new sample # mysqlを使うアプリを作成 $ rails new sample -d mysql # バージョンを指定してアプリを作成 $rails _6.0.0_ new sample # バージョン指定とmysql組み合わせ rails _6.0.0_ new sample -d mysqlコントローラー作成
# 基本形 $ rails g controller コントローラー名 # ビューも一緒に生成 $ rails g controller sample index今まで何にも知らずに
「g」
を使っていましたが、これはgenerate
を省略していたんだと知りました。
ですので本来の正しい形は$ rails generate controller コントローラー名ということです。
コントローラー名は複数形で作成する
と勉強したのですが公式をみると複数形になっていないので必須というわけではないのですかね。モデル作成
# 基本形 $ rails generate model モデル名 # 短縮 $ rails g model モデル名ローカルサーバー起動
# 基本形 $ rails server # 短縮 $ rails sローカルの
http://localhost:3000/
が起動します。ルーティング確認
$ rails routesマイグレーション関係
# マイグレーション実行 $ rails db:migrate # ロールバック(直前に実行したものだけ) $ rails db:rollback # ロールバック(戻したい数を指定することで複数戻すことができる) $ rails db:rollback STEP=戻したい数 # 状況の確認 $ rails db:migrate:status
- 投稿日:2021-02-26T10:16:05+09:00
Railsで新規作成
- 投稿日:2021-02-26T07:39:45+09:00
【Docker】エラー Could not find gem 'mysql2 (~> 0.5)' in any of the gem sources listed in your Gemfile
はじめに
Dockerの環境構築中に発生したエラーの解決した方法を記録します。
ただ、エラーは発生までの経緯で解決方法が違ってくるので、参考程度にしてください。【エラー文】
Could not find gem 'mysql2 (~> 0.5)' in any of the gem sources listed in your Gemfile環境
Docker version 20.10.0
docker-compose version 1.27.4Docker内の環境
ruby:2.6.5
Rails:6.0.0
データベース:mysqlDockerfile
docker-compose.ymlFROM ruby:2.6.5 RUN apt-get update && apt-get install -y \ build-essential \ libpq-dev \ nodejs\ vim WORKDIR /[作成したディレクトリ名] COPY Gemfile Gemfile.lock /[作成したディレクトリ名]/ RUN bundle installdocker-compose.yml
docker-compose.ymlversion: '3' services: web: build: . ports: - 3000:3000 volumes: - '.:/[作成したディレクトリ名]' tty: true stdin_open: true結論
結論は以下の2点を事項することで解決に至りました。
・bundle installしてmysqlをインストール
・webpackerをインストール経緯と対応
dockerc-composeでコンテナを作成後Railsのセットアップを行いサーバーを起動した時に発生しました。
エラー内容はmysql2が見つからない
という内容でした。対応1
Dockerfileにmysqlの記述がないから当然??と思いましたが、Gemfileにはmysqlが記述されてるのでとりあえずコンテナ内で
bundle install
をしてインストールしてみることにしてみました。かなり時間が経ってgemfileにインストールされました。
対応2
再び
rails s -b 0.0.0.0
をして起動しようと試みましたが今度はPlease run rails webpacker:install Error docker
というエラーが出ました。
これも、Dockerfileに書いてないので当然??と思いながら調べてるとwebpacker
を使う為にはyarn
が必要で、yarnをインストールする為には下記の記述も必要との事でDockerfileを編集してイメージ作成からやり直しました。
最後コンテナ内でwebpackerをインストールするとうまくいきました。対応2の手順
Dockerfileにyarnを追記します。
DockerfileFROM ruby:2.6.5 RUN apt-get update && apt-get install -y \ build-essential \ libpq-dev \ nodejs\ yarn \ ←ここ vim WORKDIR /exam COPY Gemfile Gemfile.lock /exam/ RUN bundle install
rails webpacker:install
のコマンドを入力してwebpackerをインストールします。ターミナルroot@c21d03f52523:/exam# rails webpacker:install . . . 省略 Webpacker successfully installed ? ? root@c21d03f52523:/exam#サーバーを起動します。
ターミナルroot@c21d03f52523:/exam# rails s -b 0.0.0.0 => Booting Puma => Rails 6.1.2.1 application starting in development => Run `bin/rails server --help` for more startup options Puma starting in single mode... * Puma version: 5.2.1 (ruby 2.6.5-p114) ("Fettisdagsbulle") * Min threads: 5 * Max threads: 5 * Environment: development * PID: 156 * Listening on http://0.0.0.0:3000 Use Ctrl-C to stopかなり時間がかかりましたが、インストール後
rails s -b 0.0.0.0
で見事サーバーが立ち上がりました!この記事では上記のエラー解決のみの内容ですがこの後データベースを作ってDocker内での開発環境を整えて行きます。
もしこの先にもご興味あれば下記の記事を参考にしてみてください。【Docker】Ruby2.6.5とRails6.0.0とmysql DockerComposeで環境構築
後日更新予定最後に
Dockerについて完全に理解できておらず、今回の対応も対処療法ですのでこれからも継続学習が必要です。
万が一情報が間違っている場合ご指摘していただけると幸いです。参考
- 投稿日:2021-02-26T06:53:42+09:00
Next.js + RailsでポートフォリオサイトをISR対応&メンテナンスフリー化した
2年ほど前にNuxt.jsを使ってポートフォリオサイトを作成しました。
今回、このサイトをNext.js + Railsでリニューアルしたので、経緯を記事にまとめます。リニューアル後のページ
デザインは前回のものを踏襲していて、ほとんど変わっていません。
リポジトリ
リニューアルの目的
Next.jsを使って何か作りたい
昨年からReactやNext.jsを触ってノウハウを蓄積するようにしています。私自身普段はRailsを使った開発をしているので、Next.jsを採用するとしたらRailsと組み合わせて使う可能性が高いです。
昨今のフロントエンド界隈の盛り上がりを横目に、フロントエンドにNext.js/バックエンドにRailsを用いて、何か作りたいと思っていました。
デプロイなしで内容を更新したい
前回、勢いでポートフォリオサイトを作成したものの、単なる静的ページとして公開していたので記載内容を変更するためにはVueコンポーネントを直接編集する必要がありました。
今回は管理ページを別途作成し、ログインすることで記載内容を容易に追加・変更できるようにしています。
メンテナンスフリーにしたい
リニューアル後のサイトではQiitaやZennに公開した記事、SpeakerDeckに公開したスライドをを自動的に収集し、メンテナンスしなくても内容が自動更新されるようになっています。
システム構成
Next.jsのデプロイ先としてVercelを、Railsのデプロイ先としてherokuを使っています。
また、画像の格納先としてAWSのS3を利用しました。ライブラリ・フレームワーク
フロントエンド
- React
- Next.js
- React Hook Form
- react-dropzone
- react-spinners
- react-tippy
- axios
- SWR
- Tailwind CSS
バックエンド
- Ruby on Rails
- devise
- faraday
- rails_same_site_cookie
- AWS SDK for Ruby V3
実装上のポイント
ISR(Incremental Static Regeneration)
Next.jsを使ってISRを実現しています。herokuがレスポンスを返す時間に関わらず、来訪者がすぐにページを閲覧できるようにする狙いです。
以下はISRの挙動の解説です。
Next.js(ISR有効)は、アクセスがあった際に生成済みの静的ページをレスポンスします。このとき、herokuへのアクセスは発生しません。
前回のページ生成から指定した時間を経過した後にアクセスが発生すると、静的ページを再生成します。このときNext.jsはサーバーサイドでページを再生成するのを待たず、いったん前回の静的ページをレスポンスします。
再生成が完了すると、以降その静的ページをレスポンスします。
前提として、herokuのFreeプランだと30分間アクセスがない場合にdynoがSleepするので、次にアクセスがあった場合にdynoが起動するまで数十秒ほど待たされてしまうという問題があります。本来の使い方ではないかもしれませんが、バックエンドの処理に時間がかかる場合でも生成済みのページを即時にレスポンスできるという点で、ISRは有用だと感じました。
記事・スライドの自動収集
Heroku Schedulerを使うことで、日次でrakeタスクを実行して、記事・スライドを自動収集しています。
QiitaはAPIを、ZennはFeedを使って、自分自身の記事を収集しDBに保存しています。
namespace :qiita do desc "Fetch articles from qiita" task fetch: :environment do res = Faraday.get('https://qiita.com/api/v2/users/Y_uuu/items?per_page=100') return if res.status != 200 items = JSON.parse(res.body) items.each do |item| next if Article.find_by(link: item['url']).present? item_res = Faraday.get(item['url']) next if res.status != 200 Article.create( title: item['title'], body: item['body'].truncate(100) + '...', published_at: Time.zone.parse(item['created_at']), link: item['url'], ) end end endSpeakerDeckは収集方法を悩んだのですが、よくよく調べると https://speakerdeck.com/yuuu.atom のように、自身のアカウント名の末尾に
.atom
を付与することでFeedを取得できることがわかったので、これを使って収集するようにしました。認証
最初は「SPAの認証はJWT」という思い込みがあったのですが、いろいろ調べていくうちに「cookieを使った認証でも問題ない」との結論に至りました。認証のバックエンドもRailsで、deviseというGemを使ったよくある実装です。
ただし、今回はCrossOriginな構成のためつまづきポイントが多くありました。具体的な実装方法は別記事にまとめたので、興味のある方は参照ください。
Rails 6.1対応版: APIモードのRailsに対してCrossOriginなSPAからSession認証する方法
ファイルアップロード
当初は、バックエンドがRailsということで、Active Storageを使ってファイルアップロードを実現する予定でした。実装をしていく上で「わざわざActive Storageを使う必要があるのか?」という疑問が生じ、最終的にはS3の署名付きURLを使ってアップロード・閲覧する方式に変更したという経緯があります。
こちらも別記事にまとめたので、興味のある方は参照ください。
RailsをバックエンドとしたSPAでのファイルアップロード機能の作り方に悩んだ話
感想
ISRが良い
SSRとSSGのいいとこ取りができていて良いです。SSGのようにデプロイのビルドが長くなることもなく、かつSSRを使った場合に比べてページの表示が高速なので満足です。
Vercelが良い
今回初めてVercelを使ってみたのですが、GitHubのリポジトリを指定するだけで簡単にCI/CDを構築できました。前回Netlifyを使った時も同様の感動があったのですが、とかくNext.jsを使う場合はほとんど設定が不要で、噂通りVercelとの組み合わせがベストだと実感しました。
バックエンドのRails・herokuも良い
死んだと言われて久しいRailsですが、自分にとってはやはり最速で実装ができるフレームワークです。バックエンドは必要最低限実装しつつ、フロントエンドの実装に注力するスタイルで開発が進められました。
herokuを使うことでデプロイも非常に簡単でした。
まとめ
個人的には十分満足できるポートフォリオサイトが完成しました。
Next.js + Railsで何か作ろうとしている人の参考になれば幸いです。質問・感想などありましたら、ぜひコメントをお願いします。
- 投稿日:2021-02-26T02:01:10+09:00
【rails6.0にBootstrap】RailsにBootstrapを導入する方法
はじめに
こちらの記事では、Railsのアプリケーションの開発途中でBootstrapを導入する方法について書いています。
環境
ruby '2.6.5'
rails '6.0.0'手順
1.必要なGemのインストール
まずは、必要なGemを導入(インストール)します。
Gemfilegem 'bootstrap', '~> 4.5.0' gem 'jquery-rails'ターミナル% bundle install2.CSSのファイル名変更
application.cssをapplication.scssと拡張子を変更します。
3.scssの中身を削除
application.scssファイルの中に記述されている物を全て削除します。
app/assets/stylesheet/application.scss#全て削除 ーーここから削除ーー /* * This is a manifest file that'll be compiled into application.css, which will include all the files * listed below. * * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's * vendor/assets/stylesheets directory can be referenced here using a relative path. * * You're free to add application-wide styles to this file and they'll appear at the bottom of the * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS * files in this directory. Styles in this file should be added after the last require_* statement. * It is generally better to create a new file per style scope. * *= require_tree . *= require_self */ ーーここまで削除ーー4.Bootstrapにapplication.scssにインポート
application.scssにBootstrapを使用するための記述を追記します。
app/assets/stylesheet/application.scss@import "bootstrap";5.他のSCSSファイルを読み込む記述
他のSCSSファイルを適用するためには、ファイルの数だけ記述をおこなう必要があります。
適用するための記述は以下の通りです。app/assets/stylesheet/application.scss@import "読み込みたいSCSSのファイル名";私の場合、scssファイルが7つあるので全て記述していきます。
これでブラウザをリロードすると、CSCCが適用されています。
まとめ
以上がRailsにBootstrapを導入する手順でした。
最後に、今回参考にさせていただいたQiitaの記事を貼っておきます。こちらの方は、アプリケーションの作成から丁寧に解説されています。これからアプリケーションを作成する方はぜひ合わせて読んでみてください!
- 投稿日:2021-02-26T01:51:54+09:00
Ruby 3.x, Rails 6.x, MySQL 8.x の Docker 環境構築。
概要
Docker と docker-compose を使い、アプリケーションサーバを Ruby 3.x, Ruby on Rails 6.x、DB サーバを MySQL 8.x でコンテナの構築するまでの手順となります。
なお、この記事ではセキュリティについての考慮は一切していません。
Windows 10 の WSL2 - Ubuntu 18.04 と Mac で確認していますが、後述する理由により Mac の方がお勧めです。
PostgreSQL の方が好みの方は、以下のページをご確認ください。
cf. クィックスタート: Compose と Rails前提
下記の環境が設定されていること。
Windows 10
- WSL2 Ubuntu 18.04+
- Docker
- docker-compose
- MySQL Client(必要に応じて)
Mac
- Docker
- docker-compose
- Docker Desktop for Mac
- MySQL Client(必要に応じて)
また、Docker のサービスが起動済みであること。
初期ファイル構成
以下のファイルから Rails プロジェクトを新規に作成します。
. ├── docker │ ├── app │ │ ├── Dockerfile │ │ └── entrypoint.sh │ └── db │ ├── Dockerfile │ ├── conf.d │ │ └── my.cnf │ └── initdb.d │ └── init.ddl.sql ├── scripts │ └── wait-for-it.sh ├── docker-compose.yml ├── Gemfile └── Gemfile.lock
wait-for-it.sh
は https://github.com/vishnubob/wait-for-it からいただきました。最初にchmod +x scripts/wait-for-it.sh
で実行権限を付加しておいてください。初期ファイル設定
docker-compose.yml
注意点は app 側の
build: context:
で基準になるフォルダを root として、Dockerfile のパスを指定しているところです。
これは Dockerfile 内で Gemfile をコピーする必要があるのですが、build: ./docker/app
としてしまうとフォルダを遡って Gemfile の操作ができないため、起点を root にしています。
Dockerfile が root にあれば関係ないのですが、今回は docker フォルダ下にしているため、このような対応となります。また app の
command:
で先述のwait-for-it.sh
を利用し、DB が起動するまでrails server
を立ち上げないようにしています。docker-compose.ymlversion: "3.3" services: db: container_name: "db" build: ./docker/db restart: always tty: true environment: MYSQL_DATABASE: app_development MYSQL_USER: user MYSQL_PASSWORD: password MYSQL_ROOT_PASSWORD: password TZ: 'Asia/Tokyo' command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci ports: - "3306:3306" volumes: - ./docker/db/conf.d:/etc/mysql/conf.d - ./docker/db/initdb.d:/docker-entrypoint-initdb.d networks: - backend app: container_name: "app" build: context: ./ dockerfile: ./docker/app/Dockerfile ports: - "3000:3000" environment: PORT: 3000 BINDING: 0.0.0.0 tty: true depends_on: - "db" command: ["./scripts/wait-for-it.sh", "db:3306", "--", "bundle", "exec", "rails", "s", "-p", "3000", "-b", "0.0.0.0"] volumes: - .:/app networks: - frontend - backend networks: frontend: driver: bridge ipam: driver: default config: - subnet: 192.168.10.0/24 backend: driver: bridge ipam: driver: default config: - subnet: 192.168.20.0/24app 用設定ファイル
docker/app/Dockerfile
ここで Gemfile および Gemfile.lock をホスト(ローカル)からゲスト(コンテナ)にコピーしています。
docker/app/DockerfileFROM ruby:3.0 ENV LANG C.UTF-8 ENV TZ Asia/Tokyo RUN apt-get update -qq && \ apt-get install -y --no-install-recommends sudo curl apt-transport-https wget build-essential libpq-dev nodejs default-mysql-client RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ apt-get update && \ apt-get install --no-install-recommends -y yarn RUN apt-get clean && \ rm -rf /var/lib/apt/lists/* RUN mkdir /app WORKDIR /app COPY Gemfile /Gemfile COPY Gemfile.lock /Gemfile.lock RUN bundle install COPY . /app COPY docker/app/entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000docker/app/entrypoint.sh
こちらは以下のサイトの
entrypoint.sh
からいただきました。
cf. Quickstart: Compose and Railsdocker/app/entrypoint.sh#!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /myapp/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@"db 用設定ファイル
docker/db/Dockerfile
docker/db/DockerfileFROM mysql:8.0 RUN apt-get update -qq && \ apt-get install -y --no-install-recommends locales && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* && \ locale-gen ja_JP.UTF-8 RUN sed -i -E 's/# (ja_JP.UTF-8)/\1/' /etc/locale.gen && locale-gen ENV LANG ja_JP.UTF-8 ENV TZ Asia/Tokyodocker/db/conf.d/my.cnf
my.cnf は文字コード指定が中心ですが、今回はシンプルに Rails の実行のみを考えているため、
default_authentication_plugin=mysql_native_password
で認証プラグインを変更しておきます。docker/db/conf.d/my.cnf[mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_bin default-storage-engine=INNODB explicit-defaults-for-timestamp=1 general-log=1 general-log-file=/var/log/mysql/mysqld.log default_authentication_plugin=mysql_native_password [mysqldump] default-character-set=utf8mb4 [mysql] default-character-set=utf8mb4 [client] default-character-set=utf8mb4docker/db/initdb.d/init.ddl.sql
データベースを development 以外に test 用も作成する場合のファイルです。
実際は test 用データベースはrake db:create
で作成されるはずですのでなくても問題ないと思われます。
コピー先の/docker-entrypoint-initdb.d
フォルダではシェルの実行も可能なので、組み込み次第ではいろいろと対応できるようです。docker/db/initdb.d/init.ddl.sqlCREATE DATABASE IF NOT EXISTS `app_test`; GRANT ALL ON app_test.* TO 'user'@'%';Gemfile
Gemfile
初期は Rails のバージョン指定のみとなります。
Gemfilesource 'https://rubygems.org' gem 'rails', '~>6'Gemfile.lock
初期状態は空ファイルとなります。
Gemfile.lock初期起動までの手順
rails new
Docker のサービスが起動しているか確認の上、docker-compose.yml があるフォルダで
database
を MySQL に指定してrails new
を実行します。$ docker-compose run app rails new . --force --database=mysql問題なく実行が完了すると、実行したフォルダに Rails アプリのファイル群が作成されます。
このとき、Mac の場合は実行したユーザーの権限でファイルが作成されますが、WSL の場合は root 権限となり、そのままではファイルの更新が行えません。cf. 【Docker】 WSL 2 を利用したコンテナー内開発で権限をどう設定するべきか
根本的な解決ではないとは思いますが、とりあえず以下のコマンドで権限を実行ユーザーに振り替えて対応することは可能です。
ただし、ここだけではなく、scaffold など rails のコマンドでファイルを作成・編集するごとに権限を書き換える必要があります。
(これが WSL よりも Mac をお勧めする理由です)$ sudo chown -R $USER:$USER .この時点で、作成された config/database.yml を編集し、MySQL へのアクセス設定を変更します。
config/database.ymldefault: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: user password: password host: dbdocker-compose build, up
build でサービスを作成し、問題がなければ up でコンテナを起動します。
この際、--no-cache
オプションを付けるのは、bundle install
実行時に gem ファイルを巧く取り込めない場合がある(らしい)ためです。$ docker-compose build --no-cache $ docker-compose up -dこの時、db および app がほぼ同時に立ち上がりますが、docker-compose.yml で記述した通り、app の rails server は MySQL サーバとの接続が確立するまで実行されないようになっています。
それぞれのコンテナのログはdocker logs
で確認できるため、以下のように確認して下さい。$ docker logs app # アプリケーションサーバのログ $ docker logs db # DB サーバのログ実行に問題がなければ、ブラウザまたは curl などで
http://localhost:3000/
にアクセスすることで、いつもの Rails の初期画面が表示されます。scaffold 作成と実行の確認
scaffold で MVC を作成して動作が可能か確認します。
$ docker-compose run app rails g scaffold user name:string email:stringWSL で操作している場合はファイル権限の変更をしてください。
$ sudo chown -R $USER:$USER .
db:migrate
でテーブルを作成します。$ docker-compose run app rails db:migrateブラウザで
http://localhost:3000/users
にアクセスすることで scaffold で作成した Rails 標準の UI が表示され、CRUD の一連の操作が可能なことが確認できます。
http://localhost:3000/users/new
実際の DB を確認したい場合は MySQL Client が入っていればコマンドで確認できます。
(設定を変えていない場合は、user アカウントのパスワードはpassword
となります)$ mysql -u user -h 127.0.0.1 -D app_development -pmysql> show create table users; +-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Table | Create Table | +-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | users | CREATE TABLE `users` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `email` varchar(255) DEFAULT NULL, `created_at` datetime(6) NOT NULL, `updated_at` datetime(6) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci | +-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec) mysql> select * from users; +----+------+-----------------+----------------------------+----------------------------+ | id | name | email | created_at | updated_at | +----+------+-----------------+----------------------------+----------------------------+ | 1 | test | aaa | 2021-02-25 15:56:02.123864 | 2021-02-25 15:56:02.123864 | | 2 | test | aaa@example.com | 2021-02-25 16:38:33.311981 | 2021-02-25 16:38:33.311981 | +----+------+-----------------+----------------------------+----------------------------+ 2 rows in set (0.00 sec)今回は以上となります。
参考資料
以下の記事、情報を参考にさせていただきました。
ありがとうございます。
- 投稿日:2021-02-26T01:47:14+09:00
【環境構築】docker + Vue.js + Rails + MySQL をエラー地獄切り抜け、構築した方法
はじめに
色々なサイトを見ながら、まる三日かけてついに構築できましたので、スムーズに解決できた構築方法をご紹介したいと思います。
・Ruby 2.5.8 (x86_64-linux)
・Ruby on Rails 5.2.4.5初めに、ファイル作成
$ cd 作業するディレクトリ
作業ディレクトリに移動後。
$ touch Gemfile Gemfile.lock docker-compose.yml Dockerfile
ファイル構成はこちら
[project_name] ├── docker-compose.yml │── Dockerfile │── Gemfile ├── Gemfile.lockファイルの中身を編集していきます
・Gemfile↓
source 'https://rubygems.org' gem 'rails', '~>5'docker-compose.ymlversion: '3' services: db: image: mysql:5.7.19 environment: - MYSQL_ROOT_PASSWORD=root ports: - "3307:3306" volumes: - ./tmp/db:/var/lib/mysql webpacker: build: . command: bundle exec bin/webpack-dev-server volumes: - .:/myapp ports: - "3035:3035" web: build: . environment: RAILS_ENV: development command: bash -c "bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp - bundle-data:/usr/local/bundle ports: - "3000:3000" depends_on: - db - webpacker volumes: bundle-data:db(データベース用)、webpacker、アプリケーション用、それぞれのコンテナを作成します。
・Dockerfile↓
FROM ruby:2.5 RUN apt-get update -qq && apt-get install -y build-essential libpq-dev RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - \ && apt-get install -y nodejs RUN apt-get update && apt-get install -y curl apt-transport-https wget && \ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ apt-get update && apt-get install -y yarn RUN mkdir /myapp WORKDIR /myapp COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install COPY . /myapp CMD ["rails", "server", "-b", "0.0.0.0"]Railsの初期画面を表示させる
まず、rails newでプロジェクトの作成をします。
$ docker-compose run web rails new . --force --database=mysql --webpack=vue --skip-coffee・
--skip-coffee
:CoffeeScriptのセットアップをスキップ。$ sed -i ".bak" -e "s/host: localhost/host: webpacker/g" config/webpacker.yml・
sed -i
:テキストファイルをフィルター処理で直接編集します。buildでイメージの構築をします。
$ docker-compose builddatebase.ymlを編集します。
datebase.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root # ここからした2行 password: # ここには、datebase-compose.ymlの、- MYSQL_ROOT_PASSWORD=ここを入力 host: db development: <<: *default database: myapp_developmentwebコンテナ上で、データベースの作成をします。
$ docker-compose run web rails db:createイメージ構築〜コンテナ起動までをバックグラウンド(-d)で行います
$ docker-compose up -dlocalhost:3000にアクセスすると、yey!Railsの初期画面が表示されたのではないでしょうか?
webpackerをインストールしていく
$ docker-compose run web rails webpacker:install
$ docker-compose run web rails webpacker:install:vue
$ docker-compose build
$ docker-compose up -d
Hello Vue!表示しにいく
・
config/routes.rb
を下記のように編集routes.rbRails.application.routes.draw do root to: 'home#index' end続いて、home controllerを作成していきます。
$ docker-compose run --rm web rails g controller home index
・
app/controllers/home_controller.rb
と、app/views/home/index.html.erb
の編集をするhome_controller.rbclass HomeController < ApplicationController def index end endwebpacker は
app/javascript/packs/
配下に設置されたファイルをコンパイルします。
よって、index.html.erbでは、下記のようになります。index.html.erb<%= javascript_pack_tag 'hello_vue' %>localhost:3000を開いてください。
お疲れ様でした?
参考
- 投稿日:2021-02-26T01:38:12+09:00
includeメソッド学びメモ書き
includesメソッドとは
アソシエーションの関連付けを事前に取得し
N +1問題
を解決してくれるメソッドのこと。N + 1問題
SQLが大量に発行されることで動作が重くなる問題のこと
なぜSQLが大量に発行されるのか?
(例)
user_idにuserテーブルのidを外部キーとして参照するfruitsテーブルがあったとして・・・
fruitsテーブル
id name user_id 1 りんご 2 2 みかん 3 3 バナナ 1 4 マンゴー 2 userテーブル
1 2 1 タケシ 2 サトシ 3 カスミ UserモデルはFruitモデルに対して1対他の関係になっている
Userモデルclass User < ActiveRecord::Base has_many :fruits endFruitモデルclass Fruits < ActiveRecord::Base belongs_to :user endこの関係で、「全てのユーザーのもってるフルーツを一覧表示する」コードを以下のように書くと、
userテーブルに1回アクセスが行われる(=SQLが発行される。)controller@users = User.allまた、ビューで以下のように記述して一覧表示すると、user1つずつに対してfruitsテーブルにアクセスするので、
userテーブルのレコードの数だけ(3回)SQLが発行される。view@users.each do |user| user.fruits.each do |fruit| fruit.name end endこの1+3回のSQL発行が
1+N問題
となる。includeメソッド
上記の状態で、usersテーブルにアクセスする際に、fruitsテーブルの情報ももってきてくれるのが
includeメソッド
。
引数にアソシエーションで定義した関連名を指定して定義する。(NOTテーブル名)Userモデルclass User < ActiveRecord::Base has_many :fruits #←関連名 endcontroller@users = User.all #変更 @users = User.include(:fruits) #変更後これでusersテーブルへのアクセス1回、fruitsテーブルへのアクセスは1回の計2回に変更される。
コントローラーの時点で関連テーブルの情報を一括取得することで、ビューが動いた時に必要以上のSQLが発行されない。
includeメソッドの親子関係
次に、このFruitモデルのidをさらに外部キーにする関連Juiceモデルがある場合は、以下のように書くことで、親子の子の情報もまとめて取得することができる(便利!)
Userモデル、Fruitモデル、Juiceモデル# User.rb class User < ActiveRecord::Base has_many :fruits end # fruit.rb class Fruit < ActiveRecord::Base belongs_to :user has_many :Juices end # juice.rb class Juice < ActiveRecord::Base belongs_to :fruit endusers_controller.rb@users = User.includes(fruits: :juices)上記を実行すると・・・
実行結果SELECT `users`.* FROM `users` SELECT `fruits`.* FROM `fruits` WHERE `fruits`.`user_id` IN (1, 2, 3) SELECT `juices`.* FROM `juices` WHERE `juices`.`fruit_id` IN (1, 2, 3, 4)3回のアクセスで全て取得できる!!
お疲れ様でした。
- 投稿日:2021-02-26T01:23:28+09:00
式展開ってなんなの?
式展開とは?
- 文字列の中に式を入れることができる機能
- 書き方は文字列中で
#{式}
とするだけ- 文字列を作るときにダブルクォーテーション
"
で囲む記述例と出力例
記述例"今日で#{20+1}歳になりました"表示例今日で21歳になりましたターミナルでirbを使用して確認してみる
出力成功例irb(main):001:0> "今日で#{20+1}歳になりました" => "今日で21歳になりました"
出力失敗例# シングルクォーテーションだと式展開されない irb(main):002:0> '今日で#{20+1}歳になりました' => "今日で\#{20+1}歳になりました"
シングルコーテーションは不可
シングルクォーテーション'
で囲んだ場合は式展開が行われません補足
式展開の
式
に関して
Rubyにおける「式」とは
文字列や数値の他に
- メソッドの呼び出し
- 変数
- 演算子式
などが含まれます。
すなわち、
"文字列", 1000, (1 + 5)
などはすべて式Rubyは記述をすべて式と捉えます。
ターミナル【例】irbirb(main):001:0> "記述はすべて式" # 式の結果 => "記述はすべて式"
irb
でとくにメソッドも使用せずに文字列や数値の値を確認できていた
これは、式の結果が表示されていたからこそ可能となっていた
irb
を使用して想定通りの出力がされない場合は、以下の可能性がある
- irbを起動できていない
(最初にirbとコマンドを入力しないとirbは起動しません)
- 式展開 #{ }が全角になっている
(式展開は半角で記述します)