- 投稿日:2020-05-20T22:02:32+09:00
ESC - EC2 環境でnginx + rails
- nginxとrailsを一つのサービスで定義、ネットワークのリンクでつなげる。
- railsのコンテナはポート公開しない
- ローリングデプロイや同時起動のため、nginxは動的ポートにしておく
- 投稿日:2020-05-20T21:59:13+09:00
【Rails】ActionCableを使う際のnginxの設定でハマった
はじめに
Ruby on Railsで作ったアプリにリアルタイムチャットの機能を付けたいと思って、ActionCableを使ったときの話です。
ローカル環境ではうまく動いたのですが、本番環境ではリアルタイムチャットの部分が動きませんでした。
原因を調べたところ、ActionCableで使うwebsocketという通信をするために、それに対応するようなnginxの設定が必要なようでした。
(為参考)元々のnginxの設定
元々のnginxの設定です。
修正前と修正後の差分がわかりやすいようにこちらも載せます。
ActionCableを実装する前はこれで問題なく動いていました。nginx.conf# https://github.com/puma/puma/blob/master/docs/nginx.md upstream app { server unix:///app/tmp/sockets/puma.sock; } server { listen 80; server_name ***.***.***.***; # アプリのIPアドレス keepalive_timeout 5; # static files root /app/public; location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; # static files if (-f $request_filename) { break; } if (-f $request_filename.html) { rewrite (.*) $1/index.html break; } if (-f $request_filename.html) { rewrite (.*) $1.html break; } if (!-f $request_filename) { proxy_pass http://app; break; } } location ~* \.(ico|css|gif|jpe?g|png|js)(\?[0-9]+)?$ { expires max; break; } }ActionCableに対応したnginxの設定
websocketの通信を扱えるようにするために
location /cable以下を追加しました。nginx.conf# https://github.com/puma/puma/blob/master/docs/nginx.md upstream app { server unix:///app/tmp/sockets/puma.sock; } server { listen 80; server_name ***.***.***.***; # アプリのIPアドレス keepalive_timeout 5; # static files root /app/public; location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; # static files if (-f $request_filename) { break; } if (-f $request_filename.html) { rewrite (.*) $1/index.html break; } if (-f $request_filename.html) { rewrite (.*) $1.html break; } if (!-f $request_filename) { proxy_pass http://app; break; } } #以下を追加 location /cable { proxy_http_version 1.1; proxy_set_header Upgrade websocket; proxy_set_header Connection Upgrade; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_pass http://app/cable; } #追加部分ここまで location ~* \.(ico|css|gif|jpe?g|png|js)(\?[0-9]+)?$ { expires max; break; } }
- 投稿日:2020-05-20T19:54:27+09:00
【Bootstrap】ボタンの中に無駄な四角が入ってしまう際の対処法
- 投稿日:2020-05-20T19:19:34+09:00
100日後に1人前になる新人エンジニア(0日目)
100日後に1人前になる新人エンジニア(自己紹介)
営業社員から転職をしてエンジニアになりました。
Javaでポートフォリオを作り、今後はRailsで開発を行っていく予定です。筋トレが好きです。
社会人の2年目です。
餃子も好きです。目的
- 技術的な蓄積
- アドバイスをいただく
- たくさんLGTMが欲しい
以上が主な目的です。
特に3つ目は私のエンジニアとしてのモチベーションに関わります笑100日で1人前
なれるかはわからないです。
ただ期限を持ってやることが大切だと思うので
ワニさんを見習って100日にしました。ちなみに今日はまだ0日目1日1日を大切にして進んでいきたいと思っています!
本格的な更新は明日からです。本日はまだ導入です。
お願いします
多くの人に見てもらうこと、いろんな意見をいただくことが大切だと感じています。
肯定的な意見でも、否定的な意見でもすべて僕の財産になっていくと思うので、
何か思ったことがありましたらコメントをいただけると幸いです。それではまた明日
1人前のエンジニアになるまであと100日
- 投稿日:2020-05-20T18:42:03+09:00
【RailsAPI + Vue.js】Pagyを用いたページネーションの実装
はじめに
RailsAPIとVuetifyでページネーションを作りました。
gemをどれにしようか調べてみたところ、Pagyがやたらとシンプル!軽い!ということらしいので、Pagyを使いました。環境、使用技術
- Rails 5.2.4.2
- Pagy 3.8.1
- Vue.js 4.3.1
- Vuetify 2.2.21
- axios 0.19.2
Vuetifyやaxiosは他のものでも置き換え可能かなと思います。
Rails側
Pagyの初期設定
How To | Pagyに書いてある通りです。
Gemfilegem 'pagy', '~> 3.5'毎度おなじみ
$ bundle install
を実行し、config/initializers/pagy.rb
に設定ファイルを作成します。
テンプレートをコピペして、必要なところだけコメントアウトを外します。config/initializers/pagy.rbPagy::VARS[:items] = 3 # 1ページに3件取得するコントローラ
app/controllers/api/v1/tweets_controller.rbclass Api::V1::UsersController < Api::V1::BaseController + include Pagy::Backend def index - users = User.all + pagy, users = pagy(User.all) render json: users end endAPIモードでない場合はこれだけで完成です。
PostmanでAPIを叩いてレスポンスを確認してみます。
このように、userのデータが3件ずつ取得できていました(シリアライザーを使っているので、カラム名がキャメルケースになっていますが、デフォルトではスネークケースのはずです)。しかし、これだけでは現在のページや総ページ数がわかりません。フロント側のページネーションコンポーネントではそれらのデータが必要なので、追加で記述していきます。
ヘッダーにページの情報を入れる
app/controllers/api/v1/tweets_controller.rb+ require 'pagy/extras/headers' class Api::V1::UsersController < Api::V1::BaseController include Pagy::Backend def index pagy, users = pagy(User.all) + pagy_headers_merge(pagy) render json: users end endこの記述により、レスポンスヘッダーに以下の情報が格納されます。
Left align Right align Link 最初・最後のページ、前・次のページのリンク Current-Page 現在のページ番号 Page-Items 1ページのuserの数 Total-Pages 全てのページ数 Total-Count 全てのuserの数 "Link"の中身(実際は一行)↓
<http://127.0.0.1:3000/api/v1/users?page=1>; rel="first", <http://127.0.0.1:3000/api/v1/users?page=1>; rel="prev", <http://127.0.0.1:3000/api/v1/users?page=3>; rel="next", <http://127.0.0.1:3000/api/v1/users?page=3>; rel="last"これでRails側の処理は終わりです。
共通化する場合は、after_action
を使う方法もあります(see 公式)。Vue側
Vue-routerは使っていません。
テンプレート部分
Pagination component — Vuetify.jsを少しカスタマイズします。
<template> <div class="text-center"> <v-pagination v-model="currentPage" :length="page.totalPages" ></v-pagination> </div> </template> <script> export default { data () { return { requestUrl: "/api/v1/users", page: { currentPage: 1, totalPages: 5, } } }, } </script>
これでひとまずページネーションを表示することはできましたが、まだ、ボタンを押してもpage.currentPage
の値が変わるだけです。ボタンを押したときの挙動
コンポーネントから
@input
イベントを受け取り、changePage
メソッドで処理を行います。<template> <div class="text-center"> <v-pagination v-model="currentPage" :length="page.totalPages" + @input="changePage" ></v-pagination> </div> </template> <script> export default { data () { return { + requestUrl: "/api/v1/users", page: { currentPage: 1, totalPages: 5, } } }, + methods: { + changePage(val) { + // 処理 + } + } } </script>methods: { async changePage(val) { // "/api/v1/users?page=2"などにGETリクエストを送る const response = await this.$axios.get(`${this.requestUrl}?page=${val}`) // 受け取ったusersデータを格納する const { users } = response.data this.users = users } }ページ読み込み時のデータ取得
mounted
で最初の画面描画時の動きを記述します。async mounted() { try { // "/api/v1/users"にGETリクエストを送る const response = await this.$axios.get(this.requestUrl) // それぞれのdataにレスポンスの値を代入する this.page.totalPages = Number(response.headers["total-pages"]) const { users } = response.data this.users = users } }最終的なコード
<template> <!-- usersの表示部分。省略 --> <div class="text-center"> <v-pagination v-model="page.currentPage" :length="page.totalPages" @input="changePage" /> </div> </template> <script> import goTo from "vuetify/es5/services/goto" // しれっと追加している export default { data() { return { requestUrl: "/api/v1/users", page: { currentPage: 1, totalPages: 1, }, users: [] } }, async mounted() { try { const response = await this.$axios.get(this.requestUrl) this.page.totalPages = Number(response.headers["total-pages"]) const { users } = response.data this.users = users } }, methods: { async changePage(val) { goTo(0) // ページ最上部までスクロール。Vuetifyのメソッド const res = await this.$axios.get(`${this.requestUrl}?page=${val}`) const { users } = res.data this.users = users } } } </script>ちなみに
追加でヘッダーに渡したい情報がある場合は、以下のように書くことで追加できます。
requestUrl
を初期値のdataで設定するのが難しい場合は、このようにヘッダーに渡して受け取る方法もあります。def index pagy, users = pagy(User.all) pagy_headers_merge(pagy) response.headers.merge!({ 'Request-Url' => request.url.sub(/\?page=\d+$/, '') }) render json: users end参考リンク
rails APIでページネーションを実装する
【vue.js】 Vuetifyで簡単ページネーション(Paginations)
- 投稿日:2020-05-20T16:28:47+09:00
bundle exec annotate を実行しても反応しない
状況
annotateのGemをインストールして
bundle exec annotate
を実行しても何も表示されずエラーも帰ってこない。実行後、modelsのファイルを開いてもスキーマの情報は追記されていない。
解決方法
1.annotateの設定ファイルを作成
rails g annotate:install2.設定ファイルを修正
Railsアプリケーションフォルダの
lib/tasks/auto_annotate_models.rake を開くauto_annotate_models.rake'show_indexes' => 'true',上の'true'部分を'false'に変更
auto_annotate_models.rake'show_indexes' => 'false',3.annotateを実行
bundle exec annotate
上記の方法でうまく行った場合はコマンド実行結果に以下が表示される
Annotated(数値): "modelsのファイル名列挙"その後、modelsのファイルを開くとスキーマの情報がファイル先頭に追記されている
- 投稿日:2020-05-20T16:17:13+09:00
rails の link_to
railsのhelper(link_to・form_withなど)でget以外のメソッドが使えなくなるとき。
それは、jsの設定がうまくいってないことが多々あります。
- 投稿日:2020-05-20T14:13:23+09:00
Railsで多階層カテゴリー(ancestry)にカウンター(counter_culture)を実装する
肉 > 鶏肉 > むね肉
のような多階層カテゴリーがあった時、
レシピにむね肉カテゴリーを登録したら、親カテゴリーである肉 > 鶏肉
もカウントする機能を実装してみた。ancestryを用いた多階層カテゴリーにカウンターをつけている記事が見つからず、自分で調べてみて実装したので解説してみる。
同じような人がいたらぜひ参考にしてください。
はじめに
環境
Ruby: 2.6.6 Rails: 6.0.2.2前提
レシピを掲載するサイトを作成している。
レシピDBとカテゴリーDBがある。
レシピには複数のカテゴリーがついている。(中間テーブル)
カテゴリーは多階層カテゴリになっている。
例: 肉 > 鶏肉 > むね肉
※ ancestryで実装(経路列挙モデル)1カテゴリーに何個レシピがあるかカウンターを実装している
使用しているgem
ancestry: 3.0.7 counter_culture: 2.5.1counter_cultureの導入はこちらを参照。
関連レコード数の集計(カウンターキャッシュ) - Qiita現状
Recipes Table
Table name: recipes id :bigint not null, primary key title :string not null description :string not nullRecipeとCategoryの中間テーブル
1つのレシピに対して、複数のカテゴリーが登録される関係になっている。Table name: recipe_categories id :bigint not null, primary key recipe_id :bigint not null category_id :bigint not nullCategories Table
Table name: categories id :bigint not null, primary key name :string not null ancestry :string recipes_count :integer default(0), not nullModel
class Recipe < ApplicationRecord has_many :recipe_categories, dependent: :destroy endclass Category < ApplicationRecord has_ancestry has_many :recipe_categories, dependent: :destroy endclass RecipeCategory < ApplicationRecord belongs_to :recipe belongs_to :category counter_culture :category, column_name: :recipes_count end現状の問題点
1:1の関係だと下記を中間テーブルに入れるだけでカウントされる。
counter_culture :category, column_name: :recipes_count今回は親カテゴリーも同時にカウントされる機能をつけたい。
例えば
照り焼きチキンのレシピに、むね肉のカテゴリーがついている。Recipe.find(1) => #<Recipe id: 1, title: "照り焼きチキン" > RecipeCategory.find(1) => #<RecipeCategory id: 1, recipe_id: 1, category_id: 20 > Category.find(20) => #<Category id: 20, name: "むね肉", ancestry: "1/10", recipes_count: 1 >むね肉カテゴリーは以下の関係になっている。
id:1 > id:10 > id:20
肉 > 鶏肉 > むね肉1:1のカウンターだと関連付けした「むね肉」はカウントされるが、「肉」と「鶏肉」にカウントされない。
親カテゴリーである「肉」と「鶏肉」もカウントされるように実装をしてみた。親子カテゴリのカウンター実装方法
結論からいうと、こう実装した。
class RecipeCategory < ApplicationRecord belongs_to :recipe belongs_to :category counter_culture :category, column_name: :recipes_count, foreign_key_values: proc { |category_id| Category.find(category_id).path_ids } end
foreign_key_values
は外部キーを上書きするオプション。通常だと関連づいている
category_id: 20
が外部キーとなり、Categoryのid:20のカウントが増減する。
foreign_key_values
に配列形式で数値を渡すと、渡した値を外部キーとして対象すべてのカウントを増減する。親子カテゴリーのすべてをカウントしたいため、例だと
[1, 10, 20]
の値を渡す形で実装する。実装の解説
公式のリファレンスと翻訳解説されている記事を参考にした。
GitHub - magnusvk/counter_culture: Turbo-charged counter caches for your Rails app.
Rails向け高機能カウンタキャッシュ gem 'counter_culture' README(翻訳)|TechRachoforeign_key_values: proc { |category_id| Category.find(category_id).path_ids }
proc { |category_id| }
まずprocで引数を渡す、この時に渡されるのは通常の時の外部キー(今回だと20
)
Category.find(category_id)
対象のレコードオブジェクトを取得する。
.path_ids
ancestryの機能でpath_ids
を行うとオブジェクトの親子関係のIDをリストで取得することができる。参照: 【翻訳】Gem Ancestry公式ドキュメント - Qiita
実行してみると
=> [1, 10, 20]
が返ってくることがわかる。Category.find(20).path_ids Category Load (1.0ms) SELECT "categories".* FROM "categories" WHERE "categories"."id" = $1 LIMIT $2 [["id", 20], ["LIMIT", 1]] => [1, 10, 20]
[1, 10, 20]
をforeign_key_values:
にわたすことで親子カテゴリーすべてをカウントできるようになった。カウント再計算の機能実装
counter_culture
にはcounter_culture_fix_counts
というカウントを再計算する機能が実装されている。
すでにあるデータをカウントしたり、カウントのズレを修正するために使うのだが、foreign_key_values
オプションを使うとこの機能が使えない。RecipeCategory.counter_culture_fix_counts => Fixing counter caches is not supported when using :foreign_key_values; you may skip this relation with :skip_unsupported => true調べたところ、
foreign_key_values
を使った場合、自分で再計算機能を実装するしかないようなので実装してみた。結論から、こう実装した。
すべて計算し直しているため、件数が多いと時間がかかるかもしれない。
良い実装方法があったら教えてください。class RecipeCategory < ApplicationRecord #... # Categoryのrecipes_countをすべて再計算する def self.fix_counts Category.update_all(recipes_count: 0) target_categories = pluck(:category_id) # categoriesの個数を計算する => { 1: 10, 10: 3, 20: 1 } count_categories = target_categories.group_by(&:itself).transform_values(&:size) count_categories.each do |category_id, count| count_up_categories = Category.find(category_id).path_ids Category.update_counters(count_up_categories, recipes_count: count) end end end実行している内容は以下のとおり。
- カウンターをすべて0にする
- 中間テーブルにあるcategory_idを配列で取得
- 個数を計算する => { 1: 10, 10: 3, 20: 1 }
- それぞれのカテゴリーの親子カテゴリーIDの配列を取得
- 親子カテゴリーすべてのカウントを個数分増やす
3の個数を計算する方法は、以下の記事を参考にした。
配列に同じ要素が何個あるかを数える - patorashのブログ5のカウントを個数分増やす方法は、以下の記事を参考にした。
[Rails+MySQL] カウンターの実装 【なければ新規作成したいし、あれば適切にインクリメントしたい】 - Qiita
ActiveRecord::CounterCache::ClassMethodsこれにて、
RecipeCategory.fix_counts
を実行するとカウントの再計算を行うようになった。結論
RecipeCategoryを以下にすることで親子カテゴリーのカウンター機能を実装できた。
class RecipeCategory < ApplicationRecord belongs_to :recipe belongs_to :category counter_culture :category, column_name: :recipes_count, foreign_key_values: proc { |category_id| Category.find(category_id).path_ids } # Categoryのrecipes_countをすべて再計算する def self.fix_counts Category.update_all(recipes_count: 0) target_categories = pluck(:category_id) # categoriesの個数を計算する => { 100: 3, 102: 2 } count_categories = target_categories.group_by(&:itself).transform_values(&:size) count_categories.each do |category_id, count| count_up_categories = Category.find(category_id).path_ids Category.update_counters(count_up_categories, recipes_count: count) end end endぜひ、参考にしてみてください。
FBや改善点がありましたら、コメントで教えていただけると助かります。
- 投稿日:2020-05-20T12:52:50+09:00
CircleCI × Capistranoでec2にデプロイできなくて困った。
CircleCIでCI/CDパイプラインを構築していたところ、どうしてもcapistranoの実行プロセスでSSHエラーが出るので、憤慨。結果から言うと、セキュリティグループのポートがマイIPからしか解放されないように設定していた故のエラーだったのですが、変更がめんどくさそうなのでもっといい方法がないかと模索したところ、半日くらいこのデバックに費やしてしまいました(とほほ)。
ですが、デバッグの過程で各々のツールに関する知識が深まったので、結果オーライかなぁと思うわけです。
以下、備忘録。ーーーーーーーーーーーーーーーーーーーーーーーーーーー
【原因のエラーメッセージ】
cap aborted! SSHKit::Runner::ExecuteError: Exception while executing as ユーザー名@IPアドレス: Net::SSH::ConnectionTimeout Caused by: Net::SSH::ConnectionTimeout: Net::SSH::ConnectionTimeout Caused by: Errno::ETIMEDOUT: Connection timed out - connect(2) for IPアドレス Tasks: TOP => rbenv:validate (See full trace by running task with --trace)ムムム、Capistranoの設定の時に散々遭遇した覚えがある。
実際にこの処理が行われているのは、CircleCIのサーバー上に立てられたdockerコンテナなので(合ってますよね?)、どうにかこの中に入って原因を突き止めたい。
該当するワークフローの画面から、Rerun Job with SSH を選択し、
Enable SSH に表示されるIPをコピーする。
capistranoのデプロイ先であるEC2インスタンスから、github用の秘密鍵を用いて、circleciのコンテナにSSH接続します。
(ここら辺の動きがまだよくわからん。CiecleCIにはgithubとの連携の際に公開鍵が登録されてるってことですか?)
【EC2インスタンス】 $ ssh -i ~/.ssh/aws_git_rsa -p Enable SSHの値 The authenticity of host 'Enable SSHの値 (Enable SSHの値)' can't be established. RSA key fingerprint is 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜 RSA key fingerprint is 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜 Are you sure you want to continue connecting (yes/no)?確認されるので、yesと入力。
circleci@~~~~~~~~~ :~$無事入れました。
コンテナ内を回遊してわかったんですが、結局ローカルでやろうとしてることをこのコンテナ内で再現しようとしているだけなんですよね。つまり、EC2に接続できなかったのは、ローカルの~/.sshに置かれている秘密鍵がcircleci@ :~/.sshには存在しないからなのです。思ったより単純でした。
ローカルの~/.sshにあるEC2用の秘密鍵をコピーします。
.ssh % pbcopy < ~/.ssh/himitsu_key_rsaサークルCI->プロジェクトページ->右上設定タブ->project settings
サイドバーにあるSSHkeysを選択。
Add SSH Keyから、EC2用の秘密鍵を設定します。
Hostnameを、EC2のElasticIP。Private Keyに、先ほどコピーした鍵の値をCtrl+Vで貼り付ける。値がinvalidだと怒られる。PEM形式でないと鍵の値を保存できないみたい。
参考:https://amasuda.xyz/post/2019-07-27-ssh-keygen-openssh-to-pem/
上記の記事などを参考に、鍵の形式を変更します。
BEGIN OPENSSH PRIVATE KEY が BEGIN RSA PRIVATE KEY
に変わっていれば保存できるはずです。登録を終えたら、上記画像のFingerprintの値をコピーして、.circleci/config.ymlのプロセスに記載します。
- add_ssh_keys: fingerprints: - "コピーしたFingerprint" - deploy: name: Capistrano deploy command: | if [ "${CIRCLE_BRANCH}" != "master" ]; then exit 0 fi bundle exec cap production deployここでひとまず、githubにプッシュ。実行されたプロセスを確認してみると、
fingerprintをもとに鍵の値をインストールしてくれています。
一旦コンテナの中に入って、確認してみましょう。
circleci@~~~~~~~~~:~$ ls .ssh config id_rsa id_rsa_11111111111111111111111111111111 known_hosts【id_rsa + _fingerprintから:を抜いた値】と言う形式で保存されている鍵があります(ここでは例として111...を記載しています)。
このファイルを展開してみましょう。
circleci@~~~~~~~~~:~$ cat .ssh/id_rsa_11111111111111111111111111111111 -----BEGIN RSA PRIVATE KEY----- 【さっき登録した秘密鍵の値】なるほど、さっき登録した秘密鍵は、【id_rsa + _fingerprintから:を抜いた値】という名前でCircleCIコンテナの.ssh配下に保存されているわけですね。
ローカルでは、環境変数を用いてこの鍵へのパスを指定していました。
【config/deploy/production.rb】 server 'ElasticIP', user: "ユーザー名", roles: %w{web db app}, ssh_options: { port: 22, keys: ["#{ENV.fetch('PRODUCTION_SSH_KEY')}"], forward_agent: true }しかし、ローカルとCircleCIコンテナでは鍵の名前が異なりますし、環境変数は各々のサーバーで設定する必要がありますから、CircleCI側で改めて環境変数を指定し直さなくてはなりません。
サイドバーのEnvironment Variablesを選択。
環境変数を指定します。Nameはdeproy/production.rbで使用している変数名と同じものを指定。
valueはCircleCIコンテナにある秘密鍵へのパスを指定します。ここまでくれば大丈夫なはず!再実行をしてみる!!!
cap aborted! SSHKit::Runner::ExecuteError: Exception while executing as ユーザー名@IPアドレス: Net::SSH::ConnectionTimeout Caused by: Net::SSH::ConnectionTimeout: Net::SSH::ConnectionTimeout Caused by: Errno::ETIMEDOUT: Connection timed out - connect(2) for IPアドレス Tasks: TOP => rbenv:validate (See full trace by running task with --trace)ムカつくな、試しにEC2のインバウンドルールを変更します。22番ポートを完全に解放。
まんまと成功しやがって、畜生、マジで。まあそうだよな。マイIPからのSSHじゃなきゃ開けないって設定したの自分なのに、馬鹿みたいじゃないか。
CircleCIでは毎回新しくコンテナを立ち上げている(?)のでIPアドレスが流動的です。つまり、プロセスを開始するたびに、そのコンテナからの接続を許可し、処理が終わったらそれを破棄しなければならない。
参考:https://qiita.com/rintaro-ishikawa/items/02e6a63dbc90ea67a991
この記事を参考に次の記事で問題を解決してみます。
あぁ、早くReactさわりたい。
- 投稿日:2020-05-20T12:33:56+09:00
devise ページネーション など 基礎
忘れがちな余談
コントローラーのアクション内に定義されたインスタンス変数はそのアクションのビューで使用することができる。
書くカラムに保存されている値のことをプロパティ値と言います。
ヘルパーメソッド
Railsでは予めviewでHTMLタグを出現させたり、テキストを加工するためのメソッドが用意されています。
これらをヘルパーメソッドと言います。
simple_format 改行で<br>
を付与、文字列を<p>
で囲ってくれるメソッド
form_tag フォームを出現させるメソッド
link_to aタグを出現させるメソッド
などがあります。CSSファイルの読み込みはHTMLのheadタグ内の
stylesheet_link_tag
から読み込まれます。require_treeは、引数として与えられたディレクトリ以下のCSSファイルをアルファベット順に全て読み込むという意味がある。
.
は引数であり、カレントディレクトリを表します。application.cssrequire_tree .MVC
ルーティング→コントローラー→モデル→ビュー の順に処理が行われるのがRailsです。
モデル・ビュー・コントローラーを使用して処理を行うシステムをそれぞれの頭文字をとってMVCと言います。フォーム
フォームとはユーザーが情報を入力し、その情報をサーバーに送信するためのもの。
HTMLのコード内にform要素を作成し、その中にinput要素やtextarea要素を入れることで作成できます。
formタグでもフォームは作成できるが、セキュリティの観念上、Railsではヘルパーメソッドである、form_tag, form_for, form_withを使うのが推奨されている。ユーザーがフォームに入力した値は、コントローラー内では
params
という変数に入っています。
params
はハッシュオブジェクトだと考えましょう。
ビューでフォームに入力された情報は、コントローラーにキーと一緒にパラメーターとして送られます。
このパラメーターはparams
というメソッドを使うことで取得することができます。
使用方法はparams[:キー名]form内のinputやtextareaにあるname属性の値がキー名に当たります。
ストロングパラメーター
ストロングパラメーターとは、指定したキーを持つパラメーターのみを受け取るようにするもの。
不正な情報などを受け取らないようにするのでセキュリティを強化できます。
任意のストロングパラメーター名_params
というメソッドを作成することで定義ができます。xxxxx.rbprivate def sample_params ###任意のストロングパラメーター名_params params.permit(:キー名, :キー名) endメソッドの中には
params.permit
と記述して受け取ることを許可するパラメーターのキー名を続けます。
permitメソッドは、ビューから送られてきた情報のハッシュであるparams
から後に続けたキーの名前がバリューと共に新たなハッシュとして生成します。privateメソッド
class内でprivateと記述するとそれ以降に定義したメソッドはclassの外部から呼び出せなくなります。
外部から呼ばれたら困るメソッドを守れたり、private以下は読まなくて良くなるのでコードの可読性が上がります。image_tag
Railsではロゴやバナーなど固定して表示したい画像はapp/assets/images以下に置くことが普通です。
sample.html.erb<%= image_tag 'sample.png' %>pry-rails
pry-railsとは、Gemの一つであり、Rails向けのデバッグツールです。
これを使うことによりバグの有無を確認したり処理を途中で止めてソースコードが正しいか確認できます。
特に使う機能がbinding.pry
です。
これを使うことによって処理を途中で止めて、かつrailsコンソールと同じことができます。止めたいソースコードのところでbinding.pry
を記述しましょう。変数の値を取得したり、binding.pry
を複数個設置することによって処理が正しく行われているか確認することができます。
処理を再開させたい場合はターミナルでexit
を入力します。sample_controller.def create Sample.create(sample_params) binding.pry end private def sample_params params.permit(:name, :image, :text) end投稿画面で投稿する。
(new.html.erb)するとターミナルでコンソールが立ち上がる
ターミナル.[1] pry(#<SamplesController>)> params => <ActionController::Parameters {"authenticity_token"=>"DnHBKql5oQq9lARgEnK8gQiFAhnTvIVa1XH8d13u7tuVjDUGWPM1eEjVbF44iFms57+VE7+vT3SYUePDy3lwWQ==", "name"=>"aa", "image"=>"https://kumamoto.photo/archives/_data/i/upload/2019/03/13/20190313172208-2113bf9d-la.jpg", "text"=>"dddddddd", "controller"=>"samples", "action"=>"create"} permitted: false> [2] pry(#<SamplesController>)> params[:text] => "dddddddd"コンソールで
params
と入力すると投稿画面で入力した値がキー名と共にパラメーターとして出力される。
exit
と入力すると処理が再開する。ルートパス
URLにパスを付けない、ホスト名だけのURLのことをルートパスと言います。
routes.rbでの書き方は、routes.rbroot 'コントローラー名#アクション名'orderメソッド
インスタンスを並び替えるメソッドです。
全てのレコードを取得してきた場合レコードを並び替えられます。.order("カラム名 順序の指定”)ASCは昇順 日付で言うと古い方から
DESCは降順 日付で言うと新しい方からページネーション
ページを分割させてくれるもの。1ページ当たりの表示件数を決められます。
gem kaminariを利用することで実装できます。
page
メソッド
このメソッドでページ数を指定できます。全体のページ数でなく、今何ページにいるかと言うことです。
per
メソッド
1ページあたりの表示件数を決められます。sample_controller.@samples = Sample.order("id ASC").page(params[:page]).per(10)pageメソッドの引数は、kaminari を導入した際に、モデルクラスにpageというキーが追加されておりそれを記述しています。そのキーの値はビューで指定したページの番号となります。
devise-ログイン機能-
ログイン機能を簡単に実装できるgemです。
Gemfileにdeviseを記述して
bundle install後
アプリのディレクトリで
rails g devise:install
と叩くと
設定ファイルが作成されます。
config/initialize/devise.rb
とconfig/locales/devise.en.yml
です。アカウント作成のためのモデルを作らなければいけないので
rails g devise モデルクラス名
でモデルなどの関連ファイルを作成します。
モデルファイルや、マイグレーションファイルが作成されます。
それと同時に、routes.rb
に
devise_for :モデル名の複数形
が追記されます。
devise_forはログイン周りに必要なルーティングを一気に生成してくれるdeviseのヘルパーメソッドです。current_user, user_signed_in?などのヘルパーメソッドも使えるようになっています。
rails g devise:views
でデバイスのビューファイルを作成できます。Prefix
ルーティングのパスが入った変数のことです。
unless文
if文の逆で、条件式が偽の時にする処理を記述します。
unless 条件式 偽の時に実行する処理 end 条件分岐が1行の場合は、1行で記述できる。 puts "ログインしてください" unless user_signed_in? ###ログインしてなければ(つまり偽)"ログインしてください"が表示されるredirect_toメソッド
Railsでは基本的にアクションの処理が終わるとアクションと同名のビューに遷移しますが、
redirect_toメソッド
を使えば別のアクションを実行したり、ビューに遷移させたりできます。redirect_to { action: :index }キーに
action:
をとり、値にアクションの名前のシンボル型をとります。
例えば、:index
などです。{}は省略できます。before_actionメソッド
コントローラー内のアクションが実行する前に実行したいメソッドを指定できます。シンボル型のメソッド名で指定できます。
オプションでexcept
やonly
があり、アクションごとに制限をかけられます。テーブルにカラムを追加
rails g migration Addカラム名Toテーブル名 カラム名:型configure_permitted_parametersメソッド
deviseでログイン機能を実装した際、パラメーターの受け取り方が普通とは違います。ストロングパラメーターは初期状態では、メールアドレスとパスワードのみ受け取る設定がされています。追加するにはconfigure_permitted_parametersメソッドを使います。
application_controller.rbbefore_action :configure_permitted_parameters, if: :devise_controller? def configure_permitted_parameters devise_parameter_sanitizer.permit(:追加したいアクション名, keys: [:追加するキー]) endapplication_controller.rbclass ApplicationController < ActionController::Base # protect_from_forgery with: :exception before_action :configure_permitted_parameters, if: :devise_controller? def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname]) end end
- 投稿日:2020-05-20T12:00:15+09:00
Railsで検索機能を追加する
7つの基本アクション以外でルーティングを定義する時には、collectionかmemberを利用します。
collectionはルーティングに:idがつかない、memberは:idがつく。という違いがあります。
今回は検索機能なので:idが不要なcollectionを使用します。config/routes.rbRails.application.routes.draw do resources :tweets do collection do get 'search' end end endモデルファイルに対してwhereメソッドとLIKE句を使用して検索の処理を作ります。
LIKE句は、曖昧な文字列の検索をすることができ、whereメソッドと一緒に使います。app/models/tweet.rbclass Tweet < ApplicationRecord validates :text, presence: true belongs_to :user has_many :comments def self.search(search) return Tweet.all unless search Tweet.where('text LIKE(?)', "%#{search}%") end endコントローラファイルに対して以下の記述を追加します。
app/controllers/tweets_controller.rbclass TweetsController < ApplicationController def search @tweets = Tweet.search(params[:keyword]) end end検索フォームを追加するページに検索フォームを実装します。
検索フォーム<%= form_with(url: search_tweets_path, local: true, method: :get) do |form| %> <%= form.text_field :keyword, placeholder: "投稿を検索する", class: "sample"%> <%= form.submit "検索", class: "sample"%> <% end %>app/views/tweetsに検索結果用のビューファイルsearch.html.erbを追加し、検索結果を表示させる。
app/views/tweets/search.html.erb<% @tweets.each do |tweet| %> <%= tweet.content %> <% end %>複数のカラムから検索したい場合
modelファイルを下記のように編集します。
app/models//tweetsdef self.search(search) return Tweet.all unless search Tweet.where("title LIKE(?) or content LIKE(?)", "%#{search}%", "%#{search}%") end
- 投稿日:2020-05-20T10:52:41+09:00
Railsでいいね機能を非同期で実装。js.hamlを使用
なにこれ
Railsでツイッターのように1つツイートに対して、
1人のユーザーがいいねを1回できることができ、
いいねボタンを押すと非同期で情報が変わるように作成します。注意事項
初学者のコメント機能を実装した時の備忘録として書いてます。 長々と解説してますが、まずはコピペして実践してみるのをおすすめします。 筆者が言語化して理解するためにこの記事を書いてます。 ご指摘がありましたら何なりとお申し付け下さい。
前提条件
userテーブル、tweetテーブルは作成済みとして進みます。
全体の流れ
Likeモデルとコントローラーを作成
アソシエーションを組む
ルーティングの設定
コントローラーを編集する
いいねの部分テンプレートを作る
create.js.hamlとdestroy.js.hamlファイルを作成して非同期通信が行えるようにする。
以上!LikeモデルとLikeコントローラーを作成
ターミナルrails g model Like tweet_id:integer user_id:integer rails db:migrate rails controller likes create destroyここから基本一部抜粋です
モデルとアソシエーションの設定
user.rbhas_many :likes1つのuserは複数のlikeを持つのでhas_manyです。
tweet.rbhas_many :likes, dependent: :destroy #既にいいねしているか確認するメソッド def like_user(user_id) likes.find_by(user_id: user_id) end1つのツイートは複数のいいねを持つのでhas_manyです。
ツイートが消えたらいいねも消えてほしいのでdependent: :destroyを追加します。like_userメソッドで、current_userが既にいいねをしているか確認します。
このメソッドのおかげでいいね済みor未いいねを判別してビューを変えます。like.rbbelongs_to :tweet belongs_to :user validates :tweet_id, uniqueness: { scope: :user_id }1つのいいねは1つずつのツイートとユーザーしか持たないので、belongs_toです。
バリデーションででuniquenessを追加することで
1つツイートに1つのユーザーしかいいねができないよう制限をかけます。
ルーティングの設定ルーティングの設定をします。
routesresources :tweets do #resources commentsはツイートに2つネストしてるだけです。気にしないで下さい。 resources :comments, only: [:create, :edit, :update, :destroy] resources :likes, only: [:create, :destroy] endツイートにネストされた状態にします。
コメント機能などと同様にツイートに紐付いていいねができるので。コントローラの編集
ライクコントローラーの編集
likes_controller.rbbefore_action :set_tweet def create like = Like.create(user_id: current_user.id, tweet_id: @tweet.id) like.save # @likes = Like.where(tweet_id: @tweet.id) # @tweet.reload end def destroy like = Like.find_by(user_id: current_user.id, tweet_id: @tweet.id) like.destroy # @likes = Like.where(tweet_id: @tweet.id) # @tweet.reload end private def set_tweet # @tweet = Tweet.find_by(id: params[:tweet_id]) @tweet = Tweet.find(params[:tweet_id]) end解説します。
set_tweetメソッドで、現在表示しているorボタンを押したツイートを探して取得します。
set_tweetメソッドを消す代わりに、以下のように記述してもokです。
意味は同じです。書き方が違うだけです。like = Like.create(user_id: current_user.id, tweet_id: params[:tweet_id])createアクションの解説です。
likes_controller.rbdef create like = Like.create(user_id: current_user.id, tweet_id: @tweet.id) # @likes = Like.where(tweet_id: @tweet.id) # @tweet.reload endLike.createで新しいライクを作成します。
user_idにはcurrent_user.idを代入。
tweet_idは@tweet.idで現在取得しているツイートを代入します。
like.saveなどは必要ないですが、コードの可読性を高めるために書いてます
(そのコードがどんな処理をしてるか第三者から見て分かりやすいように)destroyアクションは、いいねを削除している以外はcreateアクションとやってることは同じなので省略
likes_controller.rb# @likes = Like.where(tweet_id: @tweet.id) # @tweet.reloadコメントで伏せ字にしてある2行です。
私が参考にしたサイトは、書くことを推奨していたのですが、
自分が実験すると削除しても問題なく動作し、どこにこの変数が使われているか
分からなかったため、伏せ字にしてあります。ツイートコントローラーの中身は編集しませんが、ビューの説明用に一応載せます。
1度も@like = Like.newも作りません。直接createするので必要なかったです。tweet_controller.rbdef index @tweets = Tweet.includes(:user).order('updated_at desc').page(params[:page]).per(5) end def show @tweet = Tweet.find(params[:id]) @comment = Comment.new @comments = @tweet.comments.includes(:user).order('created_at asc') end次はビューの編集をします。
views/tweets/index.html.haml.main .tweets .tweets__list = render 'tweet'renderでツイートの一覧を表示しています。
views/tweets/_tweet.html.haml- @tweets.each do |tweet| .tweets__list--item %div{id: "tweet_like_#{tweet.id}"} = render 'likes/like', tweet: tweet下から1行目と2行目が重要です。
tweet_idというidを指定することで、コメント投稿機能と同様に
どのツイートなのかを識別しています。
js.hamlでrenderする送信先を指定している、イメージです。例)tweet_idが1なら、
になる。
いいねアクションが起こると、このidで判断して情報を更新している。イメージですviews/tweets/_tweet.html.haml= render 'likes/like', tweet: tweetrenderの後に tweet: tweetと記載してますが、これは必要な情報です。
原因は分かりませんが、この記述がないとrender先である/views/likes/_like.html.haml/
で、変数tweetが読み込んでくれません。自分の解釈だと、_tweetsで@tweetsをeach文でtweetに変えて1ツイートずつ表示しているので、文字としては同じtweetなんですが、render先だとtweetという変数が定義されてないので、tweet: tweet と記載する。みたいな感じです。
render先でも同じ変数として自動的に使わせてくれないんでしょうか??
renderさん難しい。ツイート詳細ページにもrenderでいいねページを追加します。
views/tweets/show.html.haml.like %div{id: "like_#{@tweet.id}"} = render 'likes/like', tweet: @tweet流れは先程と同じなのですが、showの場合はrenderでtweet: @tweetと指定しています。
この理由は、tweets_controllerでインスタンス変数@tweetと定義しているので、
このままだと/views/likes/_like.html.hamlで変数@tweetは使用できないので、
tweetで使えるようにしてます。次の/views/likes/_like.html.hamlが一番の難関です!!
views/likes/_like.html.haml- if user_signed_in? - if tweet.like_user(current_user.id) = link_to tweet_like_path(tweet.likes, tweet_id: tweet.id), method: :delete, remote: true do %i.fa.fa-heart{"aria-hidden" => "true", style: "color: red;"} = tweet.likes.count - else = link_to tweet_likes_path(tweet.id), method: :post, remote: true do %i.fa.fa-heart{"aria-hidden" => "true", style: "color: #C0C0C0;"} = tweet.likes.count - else %i.fa.fa-heart{"aria-hidden" => "true"} = tweet.likes.count dden" => "true"} = item.likes.countrails routesも確認します。
tweet_likes POST /tweets/:tweet_id/likes(.:format) likes#create tweet_like DELETE /tweets/:tweet_id/likes/:id(.:format) likes#destroyここは大事なので全部の行で解説します。上から順に行きます。
ユーザーがサインインしてるか判別
like_userメソッドでそのツイートに対して既にいいねしてるか判別します。
既にいいねしてる場合は、いいねを削除するリンクを表示させます。views/likes/_like.html.hamltweet_like_path(tweet.likes, tweet_id: tweet.id)この書き方で、likes#destroyを呼び出すことができます。
ネストしたリンク先を指定するのが複雑すぎる。詳しい方にご教授願いたいです。
likesの場合は複数あるからlikesだけど、commentとかの場合だと1つしかないから単数形にしないとダメみたいな感覚です。commentが複数だったら日本語的にもおかしいもんね自分が調べた限りですと、以下の書き方でも正解でした。
views/likes/_like.html.hamltweet_id: tweet.id ,tweet.likes[0].id #likes[0]ってどういうこと笑。これが一番よく分からなかった。 tweet.id ,tweet.likes #これが分かりやすいと思った。 tweet, tweet.likes #すごい短い tweet.likes, tweet_id: tweet.id #わかり易さ重視。ちなみに順番入れ替えるとsyntax errorエラー出ました。よく分からん。%iタグはボタン表示です。
tweet.likes.countでいいね数の一覧を表示します。
そのツイートが所持?繋がっている?いいね数です。
tweet.like_user(current_user.id)でelseだったら、まだいいねをしてないので
新しくいいねができるリンクを表示させます。
rails routesを見てもらえたら分かると思うんですけど、
新規投稿は1回しかid指定が必要ないから簡単なんですよね。
その後は先程と同じです。ユーザーがサインインしてない場合は、リンクを表示せずにいいね数だけをカウントします。
js.hamlファイルを作成していきます
views/likes/create.js.haml$("#like_#{@tweet.id}").html("#{j(render partial: 'likes/like', locals: { tweet: @tweet })}");views/likes/destroy.js.haml$("#like_#{@tweet.id}").html("#{j(render partial: 'likes/like', locals: { tweet: @tweet })}");はい、実はどちらも中身は完全に同じです。
/tweets/_tweetと/tweets/showで指定してたidの部分に
renderしてフォームを送って部分テンプレートを呼び出して更新させるイメージです。locals: { tweet: @tweet }これの意味は正直理解できてません。でもこれがないと、
それぞれcreateとdestroyアクション発火時にDB上にデータは保存される
(いいねしたのは追加されてる)けど、非同期通信でエラーが発生します。
500エラーが出て部分テンプレートが更新されません。以下がエラー文です。POST http://localhost:3000/tweets/5/likes 500 (Internal Server Error)自分なりの解釈ですが、出来る限り説明します。
結論だけ先に言うと、renderでインスタンス変数@tweetを変数tweet両方とも使えるようにした。だと思います。
いいねでcreateアクションが起きるとどこの画面が更新されるかの流れで考えます。
tweet/からいいねを押した場合だと、
いいねをした時にまずrenderする前である_tweetに戻ります。
_tweetだと、@tweetsをeachしてtweetで繰り返し処理してます。
再度renderする前まで読み込まれて、render先でも変数tweetが使えるようになって無限ループする。みたいなイメージです。
でも、このイメージだと、2つ問題があります。
create.js.hamlで$("#like#{@tweet.id}”)という記述がありました。
この場合だと、_tweetの時のビューの記述は以下のようでした。_tweet.html.haml%div{id: "like_#{tweet.id}"}はい、idの指定部分がおかしいです。
先程の_likeまでの流れだと、インスタンス変数@tweetを変数tweetにしてました。
create.js.hamlの記述だと、tweetのidを指定する変数が違うので読み込まないと思いました。でも結論で言ったとおり変数が2つ両方とも使えるっていう認識だったら上手くいく。tweets/showの方は文字通りだったので、省略
_tweetとshowでidを使い分けたほうが、
可読性は高まりそうだけど動くからこれでいいかなー
localより後に書いた部分は消せないし、renderって難しい。今回も長くなりましたが、以上になります!
ここまで読んでいただきありがとうございます!
- 投稿日:2020-05-20T09:42:45+09:00
Railsで画像を使用する
- 投稿日:2020-05-20T09:34:24+09:00
フォームをGETで送信するときは送信したいパラメータをURLではなくhiddenタグに設定する必要がある、という話
はじめに:イントロダクション
以下のような単純なRailsのフォームがあったとします。
<%= form_tag foo_blogs_path(some_flag: 1) do %> <%= button_tag 'Foo' %> <% end %>コントローラのコードは次のように
some_flag
の値をnoticeに出力するようになっています。class BlogsController < ApplicationController def foo redirect_to :blogs, notice: "Foo / some_flag: #{params[:some_flag]}" end end以下はFooボタンをクリックしたときの実行結果です。
フォームのURLに設定したsome_flag: 1
の値が表示されています。本題:困ったこと
上の例と同じようなことを以下のようなフォームでもやろうとしました。
<%= form_tag bar_blogs_path(some_flag: 1), method: :get do %> <%= button_tag 'Bar' %> <% end %>先ほどのコード例と違うのはフォームのHTTPメソッドがGETになっている点(
method: :get
)です。コントローラ側のコードはほぼ同じです。
class BlogsController < ApplicationController def bar redirect_to :blogs, notice: "Bar / some_flag: #{params[:some_flag]}" end endしかし、Barボタンをクリックしてみると・・・
あれっ、
some_flag
の値が空になってる!ログを見るとこんなふうになっていて
some_flag
の値が渡されていません。Started GET "/blogs/bar?button=" for 127.0.0.1 at 2020-05-20 09:13:49 +0900出力されたHTMLを見てみると、
action="/blogs/bar?some_flag=1"
のようにsome_flag
の値がちゃんと設定されているように見えます。<form action="/blogs/bar?some_flag=1" accept-charset="UTF-8" method="get"> <button name="button" type="submit">Bar</button> </form>なんでだろう、おかしいなあ・・・。
原因を突き止めるためにいろいろ試行錯誤したものの、some_flag
の値をサーバーに送信することができず、この日は諦めて寝ることにしました。「こんな仕様変更、楽勝やわー」と思ってコードをいじったけど、なぜか思い通りに動いてくれず、「たぶんこれが原因かな?」と雑な推測を元にコードを書き換えたけど、それでも動かず、何度か試行錯誤して「うーん、今日はもうダメだな」と仕事を切り上げた。一晩頭を冷やせばきっとすぐ解決するはず!!
— Junichi Ito (伊藤淳一) (@jnchito) May 18, 2020翌日・・・解決しました!
次の日、デバッグを再開したところ、今度は原因と解決策がわかりました。
フォームをGETで送信する場合は、action
のURLではなくhiddenタグに送信したいパラメータを含める必要があるのでした。<%= form_tag bar_blogs_path, method: :get do %> <!-- hiddenタグに送信したいパラメータを含める --> <%= hidden_field_tag :some_flag, 1 %> <%= button_tag 'Bar' %> <% end %>こうすれば
some_flag
の値をサーバーに送信することができます!ログを見ても
some_flag=1
が送信されていることがわかります。Started GET "/blogs/bar?some_flag=1&button=" for 127.0.0.1 at 2020-05-20 09:22:21 +0900これで解決です。めでたしめでたし。
そういえば、昨日困っていた謎の挙動は予想通り「冷えた頭」で見直したら原因が見つかりました✨
— Junichi Ito (伊藤淳一) (@jnchito) May 19, 2020
他の人も僕と同じようにうっかりハマりそうな落とし穴だったので、そのうちQiitaとかに書くかも〜。
- 投稿日:2020-05-20T00:50:08+09:00
ターミナルとは?-絶対パス&相対パス-
ターミナルとは
ターミナルとは、コマンドラインの一つで、PCに命令を与えるためのツールです。記述したコードのファイルを実行するのに必要となります。
コマンドラインとは、コマンドラインインターフェース(CLI)の略で、PC対してキーボードからコマンドという文字を打ち込んで操作を行う方法です。
これとは対照に、グラフィックを用いてPCの操作を行う方法をグラフィカルユーザーインターフェース(GUI)と言います。
コマンド
ターミナルでよく使うコマンドを紹介します。
cd
コマンド
デイレクトりを移動するコマンドです。
ls
コマンド
現在いるディレクトリの直下にあるファイルやフォルダを表示します。
ディレクトリとは
- ディレクトリとはファイルの入れ物のようなものでフォルダを指します。 ディレクトリのは階層構造になっており、 その一番上の階層のディレクトリのことをルートディレクトリと言います。 自分が現在いるディレクトリのことをカレントディレクトリと言います。 また、ターミナルを開いた時に表示されるディレクトリのことをホームディレクトリと言います。Macでのデフォルトのホームディレクトリは/Users/ユーザー名です。
話戻ります。
cd
コマンドを使ってディレクトリを行き来できるわけですが、cd
コマンドに続けてパスを入力することによって目的のディレクトリに辿り着くことができます。そのパスに記述の仕方には2通りあります。
・絶対パス
・相対パスです絶対パス
- 絶対パスとはルートディレクトリから記述するパスのことです。
/
から記述するのが決まりです。ターミナル.$ cd / ###これでルートディレクトリに移動できますMacのデフォルトのルートディレクトリはMacintosh HDだと思いますので
cd /
でMacintosh HDに移動します。
ls
コマンドで直下のディレクトリを確認すればパスがわからなくても確認して移動できます。相対パス
相対パスは先頭に
/
をつけません。
現在いるディレクトリの直下にあるディレクトリから記述することによって移動することができます。この記事を読んでいただきありがとうございました。
- 投稿日:2020-05-20T00:29:59+09:00
Railsで「Could not find a JavaScript runtime」というエラーが出てrails s ができなくなった
rails s ができない
久しぶりにRailsを触り、rails sでサーバー起動させると
長文のエラーが発生し、一番下に「Could not find a JavaScript runtime」と書いてありました。解決策
調べたところ解決策は2つあるみたいです。
・gem 'therubyracer'
をGem fileに追記しbundle install
を実行する。
・Node.jsをhttps://nodejs.org/en/からインストールする。GitHub ExecJS
https://github.com/sstephenson/execjs
私はNode.jsをインストールし
「rails s」できるようになりました。