HerokuでgitpushしようとするとPermission denied (publickey).になる
読んだもの
学んだこと
会社でつかっていたリポジトリをクローンして herokuに変更をpushしようとしたところ、下記のエラーになった。
$ git push heroku master Warning: Permanently added the RSA host key for IP address '50.19.85.156' to the list of known hosts. Permission denied (publickey). fatal: Could not read from remote repository.
会社のPCではpublic keyを登録していたが、自宅PCでは登録していなかったのが原因。
$ heroku keys:add
上記コマンドでpublic keyを登録すればOK.
find_or_create_byとfind_or_initialize_byの違い
読んだもの
学んだこと
両者の違いは下記の通り。
- find_or_create_byだと新規作成して保存
- find_or_initialize_byして保存はしない
保存しないことのメリットは、新規作成するときのみ併せて何かする、という処理ができること。 find_or_create_byは新規作成と同時に保存してしまうので、新しいレコードなのか既にあるレコードなのか判断できず、上記のような処理はできない。
例えば、下記のように使う。
@user = User.find_or_initialize_by(name: 'test') unless @user.persisted? # @user が保存されていない場合の処理を書く @user.save end end
Herokuアプリを削除する
git remoteから削除するのを忘れがち。
Heroku上のアプリを削除
$ heroku apps:destroy --app { app_name }
git remoteから削除
$ git remote rm heroku // 確認する $ git remote -v
RailsのアプリケーションをHerokuにデプロイする流れ
たまにしか一連の流れをやらないので、いつも何かを忘れてしまう。 まとめておく。
コマンドラインツールのインストール
$ wget https://cli-assets.heroku.com/heroku-cli/channels/stable/heroku-cli-linux-x64.tar.gz -O heroku.tar.gz $ sudo mkdir -p /usr/local/lib/heroku $ sudo tar --strip-components 1 -zxvf heroku.tar.gz -C /usr/local/lib/heroku $ sudo ln -s /usr/local/lib/heroku/bin/heroku /usr/local/bin/heroku
ターミナル上でHerokuにログイン
$ heroku login Enter your Heroku credentials. Email: { mail_address } Password: { pass }
Heroku アプリを作成
$ heroku create { app_name } // 作成されたことを確認 $ heroku apps { app_name } // リモートにあることを確認 $ git remote -v heroku https://git.heroku.com/app_name.git (fetch) heroku https://git.heroku.com/app_name.git (push) origin https://github.com/mom0tomo/app_namegit (fetch) origin https://github.com/mom0tomo/app_name.git (push)
DBの設定
Heroku の標準データベースはPostgreSQL。 Rails 側でPostgreSQLを使用するために設定をする。
Gemfile
group :production do gem 'pg', '0.21.0' end
--without productionを指定してbundle installする。
$ bundle install --without production
config/database.yml
production: adapter: postgresql encoding: unicode pool: 5 database: { app_name }_production username: { app_name } password: <%= ENV['APP-NAME_DATABASE_PASSWORD'] %>
Herokuにpush
$ git push heroku master
マイグレーション
$ heroku run rails db:migrate
【オプション】PostgreSQL アドオンの追加
下記のようなエラーが出る場合がある。
PG::ConnectionBad: could not connect to server: No such file or directory Is the server running locally and accepting connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"?
Heroku 上でPostgreSQL がインストールされていないせいで起きるエラー。
デプロイ時に config/database.yml がちゃんと設定されていないと、自動的に PostgreSQL がインストールされないので、手動でインストールする。
$ heroku addons:create heroku-postgresql:hobby-dev
Rspecで"You must pass an argument rather than a block...." エラー
読んだもの
学んだこと
matcherにはブロックを受け取れるものとそうでないものがある。
例えば以下のように書くと表題のエラーになる。
context 'とある場合' do it '名前がほげほげであること' do expect { described_class.new(user).name }.to eq "ほげほげ" end end
You must pass an argument rather than a block to use the provided matcher....
これはeqというmatcherはブロック {}
を受け取れないというエラー。
下記のように引数にすることで実行できる。
context 'とある場合' do it '名前がほげほげであること' do expect(described_class.new(user).name).to eq "ほげほげ" end end
何気なく他の部分のテストを真似るとときどきこのようなエラーを招くので気をつける。
多対多のリレーションをhas_manyでモデルに実装する
Railsでは、ActiveRecordのhas_manyかhas_and_belongs_to_manyを使うことで、多対多のテーブルどうしの関連をモデルに実装できる。
基本的には拡張性のあるhas_manyを使った方がよい。
マイグレーションファイルをつくる
Twitterクローンの例をもとに、userが micropost を likeする例で考える。 usersテーブルとmicropostsテーブルを作成し、中間テーブル(likesテーブル)に両方のテーブルの外部キーを定義する。
$ rails g model User name:string $ rails g model Micropost content:string $ rails g model Like user_id:integer micropost_id:integer
モデルにthroughオプションをつける
throughオプションにより、usersからlikes経由でmicropostsにアクセスできるようになる。 つまり、user.micropostsという形が使える。
app/models/user.rb
class User < ActiveRecord::Base has_many :likes has_many :microposts, through: :likes end
micropostからuserも同じく、micropost.usersと言う形が使える。
app/models/micropost.rb
class Micropost < ActiveRecord::Base has_many :likes has_many :users, through: :likes
中間テーブルとの関係はbelongs_toで表す。
app/models/like.rb
class Like < ActiveRecord::Base belongs_to :user belongs_to :micropost end
【解消法】has_secure_passwordを使おうと思ったらbcryptでエラー
こんなエラーが出た。
cannot load such file -- bcrypt
bcrypt gemでよくあるエラーらしい。
読んだもの
https://github.com/codahale/bcrypt-ruby/issues/142#issuecomment-291345799
学んだこと
解消法
$ gem uninstall bcrypt $ gem uninstall bcrypt-ruby $ gem install bcrypt --platform=ruby
Gemfileのbcryptのバージョンを下記に変更する。
gem 'bcrypt', '~> 3.1.11'
$ bundle install
して、サーバーを再起動する。
原因
$ rails _5.0.6_ new
したときにGemfileに自動で書き込まれるバージョン gem 'bcrypt', '~> 3.1.7'
に不具合がある模様。
3.1.11以降に解決されているため、3.1.7関係を一度アンインストールして、新しい方をインストールし直す。
Homebrewで管理しているElasticsearchにプラグインをインストールする
読んだもの
学んだこと
Elasticsearch::Transport::Transport::Errors::BadRequest:[400] No handler found for [400] No handler found for analysis-icu
などというエラーが出たので、analysis-icuをインストールしようとした。
公式だと
$ bin/elasticsearch-plugin install [plugin_name]
でインストールするように書いてあるが、brewでElasticsearchを管理している場合はパスが異なる。
brew管理の場合は
$ /usr/local/bin/elasticsearch-plugin install [plugin_name]
でインストールする。
【メモ】herokuにRailsアプリケーションをデプロイするときのコマンド
うっかり忘れがちなので自分用にメモにまとめておく。
// ログイン $ heroku login // アプリケーション作成 $ heroku create my-rails-app // 作成されたか確認 $ heroku apps my-rails-app // herokuにpush $ git push heroku master // pushされたか確認 $ git remote -v heroku https://git.heroku.com/my-rails-app.git (fetch) heroku https://git.heroku.com/my-rails-app.git (push) origin https://github.com/mom0tomo/my-rails-app.git (fetch) origin https://github.com/mom0tomo/my-rails-app.git (push) // herokuでdb:migrate $ heroku run rails db:migrate
heroku run rails db:migrate
忘れがち。
Rails kaminariでページネーションを実装する
読んだもの
学んだこと
kaminariの使い方はすごく簡単。
tasks_controller.rb
def index @tasks = Task.all.page(params[:page]) # @tasks = Task.all <- kaminari利用前のコード end
index.html.erb
<h1>タスク一覧</h1> <ul> <% @tasks.each do |task| %> <li> <%= link_to task.id, task_path(task) %> : <%= task.content %> </li> <% end %> </ul> # ページネーションしたいところに記述する <%= paginate @tasks %> <%= link_to '新しいタスクを登録', new_task_path %>
controllerのTask.all部分に .page(params[:page])
を追加して、viewのページネーションしたいところに <%= paginate @tasks %>
をつけるだけ。
表示数を変更する
1ページの表示数はデフォルトで26に設定されており、変更したいときは .per(number)
をつける。
def index @tasks = Task.all.page(params[:page]).per(10) end
表示順番を降順にする
表示順番を降順にしたい場合は、Controller側で order
を指定する。
tasks_controller.rb
def index @tasks = Task.order(created_at: :desc).page(params[:page]).per(10) end
こんな感じ。
Railsでパーシャルを切り出すときはインスタンス変数をローカル変数にする
new.html.erb
<h1>新規タスク作成</h1> <%= form_for(@task) do |f| %> <% f.label :content, 'タスク' %> <% f.text_field :content %> <% f.submit '登録' %> <% end %> <%= link_to '一覧に戻る', tasks_path %>
edit.html.erb
<h1>タスク編集画面</h1> <p><%= @task.content %></p> <%= form_for(@task) do |f| %> <% f.label :content, 'タスク' %> <% f.text_field :content %> <% f.submit '登録' %> <% end %> <%= link_to '一覧に戻る', tasks_path %>
こういうのがあったときに、共通部分(フォーム周りのところ)をパーシャルにして切り出したい。
学んだこと
以下のようなパーシャルをつくる。
_form.html.erb
<%= form_for(task) do |f| %> <%= f.label :content, 'タスク' %> <%= f.text_field :content %> <%= f.submit '登録'%> <% end %>
new.html.erb
<h1>新規タスク作成</h1> <%= render 'form', task: @task %> <%= link_to '一覧に戻る', tasks_path %>
edit.html.erb
<h1>タスク編集画面</h1> <p><%= @task.content %></p> <%= render 'form', task: @task %> <%= link_to '一覧に戻る', tasks_path %>
ポイントは以下の通り。
RailsでviewのフォームからcontrollerにPOSTでデータを送信する
RailsのCRUDの基本のところで、newアクションからcreateアクションにデータを送るあたりがよくわからなくなったのでまとめる。
学んだ事
formタグのPOSTメソッドとパラメータの関係
まずはRailsではなく素のHTMLで考える。
<form action="/" method="POST"> <label>名前: <input type="text" name="target_name"></label> <input type="submit" value="送信"> </form>
viewにこのようなフォームがある場合、
- ユーザからのアクションは全て、HTTPリクエストのGETメソッドやPOSTメソッドとしてWebサーバに送信される。
- HTTPリクエストのPOSTメソッドで送ったデータは、params(パラメータ)に格納される。
- Webサーバはリクエストをもとに、paramsに入っているユーザからのデータを処理し、レスポンスを返す。
これが基本。
Railsのnewアクション
app/controller/task_controller
class TasksController < ApplicationController before_action :set_task, only: [:show, :edit, :update, :destroy] def index @tasks = Task.all end def show end def new @task = Task.new end end
newアクションでは、対応するviewをPOST メソッドを送信する新規作成用の入力フォーム置き場として使う。
app/views/messages/new.html.erb
<h1>タスク新規作成ページ</h1> <%= form_for(@task) do |f| %> <%= f.label :content, 'タスク' %> <%= f.text_field :content %> <%= f.submit '登録' %> <% end %> <%= link_to '一覧に戻る', messages_path %>
上のようなviewを書く事で、POSTメソッドを使ったフォームが生成される。
<body> <div class="container"> <h1>新規タスク作成</h1> <form class="new_task" id="new_task" action="/tasks" accept-charset="UTF-8" method="post"> // POSTメソッドを使う <input name="utf8" type="hidden" value="✓" /> <input type="hidden" name="authenticity_token" value="1XF/tmSKY6ak0LovbntLa/AqaxPSPAG6Ak5YyBdA4W3ebzM8KilBV+Vxw5adGwhqPE7pEJ9mn3ZbgJdGKQsJ1w==" /> <label for="task_content">タスク</label> <input type="text" name="task[content]" id="task_content" /> <input type="submit" name="commit" value="登録" data-disable-with="登録" /> </form> <a href="/tasks">一覧に戻る</a> </div> </body>
viewから実際に生成されるHTMLはこんな感じになる。
Railsのcreate アクション
app/controller/task_controller
class TasksController < ApplicationController .... def new @task = Task.new end def create @task = Task.new(task_params) respond_to do |wants| if @task.save flash[:success] = 'Taskが登録されました' redirect_to @message else flash.now[:danger] = 'Taskが登録されませんでした' render :new end end end private def set_task @task = Task.find(params[:id]) end # ストロングパラメータ def task_params params.require(:task).permit(:content) end end
create アクションは、newのviewからPOSTで送信されたフォームのデータを処理する。
new からcreateへ送られてきたフォームの内容は params[:task]
に入る。
params[:task]
をそのまま使用するのはセキュリティ上よくないので、ストロングパラメータを使ってフィルタする。
パラメータがネストしているときのストロングパラメータの書き方
前回の応用編。
読んだもの
学んだこと
ネストしたパラメータのサンプル
{ "name": "momo", "address": { "prefecture": "Toyo", "city": "Shinagawa" } }
上記のようなネストした構造のパラメータについて、ストロングパラメータの書き方は以下の通り。
user_controller.rb
params.permit(:name, address: [:prefecture, :city])
ストロングパラメータの簡単なサンプル
読んだもの
学んだこと
- Railsではストロングパラメータを必ず使う
- フォームの受け渡しなどで使わないとエラーになる(警告)
ストロングパラメータの基本的な書き方
params.require(:user).permit(:name, :email, :password)
やっていること
以下の二つ。 1. requireでPOSTで受け取る値のキーを設定している 2. permitで許可するカラムを設定している
上の例ではname, email, password属性の値だけDBに入れるのを許可している。
サンプルコード
実際に書くときはこんな感じ。
task_controller.rb
class TasksController < ApplicationController def index @tasks = Task.all end def show @task = Task.find(params[:id]) end def update @task = Task.find(params[:id]) if @task.update(task_params) redirect_to @task else flash.now[:danger] = 'Message は更新されませんでした' render :edit end end private # ストロングパラメータ def task_params params.require(:task).permit(:content) end end