dev/mom0tomo

技術メモ

HerokuでgitpushしようとするとPermission denied (publickey).になる

読んだもの

opamp.hatenablog.jp

学んだこと

会社でつかっていたリポジトリをクローンして 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の違い

読んだもの

blog.hello-world.jp.net

学んだこと

両者の違いは下記の通り。

  • 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

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 の標準データベースはPostgreSQLRails 側で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...." エラー

読んだもの

www.d-wood.com

学んだこと

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関係を一度アンインストールして、新しい方をインストールし直す。

Visual Studio CodeでGoを書くために入れているプラグイン

GoLandを買うか迷ったけど、無料で使えるVSCodeにした。

業務ではRailsしか書かないのでRubyMine、GoはVSCodeと言語でエディタを分けて使っている。
さらっと文章やyamlを書くときは一番手に馴染んでいて軽いSublimeがやっぱり使いやすい。

VScodeのGo関連プラグイン

  • Go
  • Go Custom Format
  • Go Doc
  • Go Outliner
  • Go Tests Outline
  • Go Themes(playground & src)

この辺りを入れている。

Homebrewで管理しているElasticsearchにプラグインをインストールする

読んだもの

www.elastic.co

学んだこと

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でページネーションを実装する

読んだもの

github.com

学んだこと

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 %> 


ポイントは以下の通り。

  • パーシャルでは元のインスタンス変数 @taskをローカル変数 taskに変更している
  • new/editでは task: @taskとすることでパーシャルに対して変数を渡している
    • taskという変数名で @task の値を渡している
    • パーシャルにインスタンス変数を使わないことでより汎用的になる

RailsでviewのフォームからcontrollerにPOSTでデータを送信する

RailsCRUDの基本のところで、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="&#x2713;" />
      <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] をそのまま使用するのはセキュリティ上よくないので、ストロングパラメータを使ってフィルタする。

パラメータがネストしているときのストロングパラメータの書き方

前回の応用編。

読んだもの

qiita.com

学んだこと

ネストしたパラメータのサンプル

{
  "name": "momo",
  "address": {
    "prefecture": "Toyo",
    "city": "Shinagawa"
  }
}

上記のようなネストした構造のパラメータについて、ストロングパラメータの書き方は以下の通り。

user_controller.rb

params.permit(:name, address: [:prefecture, :city])

ストロングパラメータの簡単なサンプル

読んだもの

qiita.com

学んだこと

  • 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