dev/mom0tomo

技術メモ

Railsアプリを作るときの共通パーツの設定

初期設定はこちら。 mom0tomo.hateblo.jp

共通レイアウトはapp/views/layouts/ に配置する。

headタグのパーツ

app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>アプリケーション名</title>
    <%= csrf_meta_tags %>

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <%= stylesheet_link_tag  'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
  </head>

  <body>
    <%= render 'layouts/navbar' %>

    <div class="container">
      <%= render 'layouts/flash_messages' %>

      <%= yield %>
    </div>
  </body>
</html>

flashメッセージのパーツ

app/views/layouts/_flash_messages.html.erb

<% flash.each do |message_type, message| %>
  <div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>

flash[:success] / flash[:danger]などよく使う。

エラーメッセージのパーツ

app/views/layouts/_error_messages.html.erb

<% if model.errors.any? %>
  <div class="alert alert-warning">
    <ul>
      <% model.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
    </ul>
  </div>
<% end %>

以下は必須とは限らないがだいたい必要になるパーツ。

ナビバーのパーツ

app/views/layouts/_navbar.html.erb

<header>
  <nav class="navbar navbar-inverse navbar-static-top">
    <div class="container">
      <div class="navbar-header">
        <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
          <span class="sr-only">Toggle navigation</span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
        <a class="navbar-brand" href="/">アプリケーション名</a>
      </div>
      <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
        <ul class="nav navbar-nav navbar-right">
          <li><a href="#">Signup</a></li>
          <li><a href="#">Login</a></li>
        </ul>
      </div>
    </div>
  </nav>
</header>

Railsの便利なコマンドと設定

新規アプリケーション作成するとき

DBの種類を設定する

$ rails new {app_name} --database=mysql

テストを作成しない

$ rails new {app_name} --skip-test

bundle installするとき

gemのインストールパスを指定する

$ bundle install --path vendor/bundle

Production環境のgemを除いてインストールする

$ bundle install --without production

CSS/JS/Helper/Routingを自動生成しない

config/initializers/generators.rbを作成し設定を書く。

Rails.application.config.generators do |g|
  g.stylesheets false
  g.javascripts false
  g.helper false
  g.skip_routes true
end

おまけ
gitignoreはこの辺りを設定しておく。
https://www.gitignore.io/api/vim,ruby,macos,rails,rubymine,intellij+all

モデル名と異なるアソシエーション名を使う場合の決まり

Rails tutorialでも出てくるuserがfollowings/followers(どちらもuserテーブルを使う)を持つ関係を例にまとめる。

models/user.rb

class User < ApplicationRecord

  # followingsのアソシエーション
  has_many :relationships
  has_many :followings, through: :relationships, source: :follow

  # followersのアソシエーション
  has_many :reverses_of_relationship, class_name: 'Relationship', foreign_key: 'follow_id'
  has_many :followers, through: :reverses_of_relationship, source: :user
end

中間テーブルはrelationshipsとする。

models/relationship.rb

class Relationship < ApplicationRecord
  belongs_to :user
  belongs_to :follow, class_name: 'User'
end

class_nameについて

モデル名_id という名前になっている場合、Railsでは自動的にモデルを参照する。 しかし follow_id のように命名規則に従っていないものを使う場合は、 class_name: 'User'と補足設定する必要がある。

through, sourceについて

既存のリレーションから中間テーブルを経由して向う側にあるモデルを参照するようにするためには、 through: ... , source: ...を使用する。

  has_many :followings, through: :relationships, source: :follow

これにより、 user.followingsと書けば、該当のuser がフォローしている User群を取得できるようになる。

仕組み

  • has_many :followings
    • usersとfollowingsの関係を新しく命名し、フォローしているUser群を表現している。
    • Followingというモデルは無いため、この後ろに取得する情報の補足を付け足している。
  • through: :relationships
    • has_many: relationshipsの結果を中間テーブルとして指定している。
  • source: :follow
    • 中間テーブルのカラムの中でどれを参照先の id とすべきかを選択している。
    • user_id というカラム名にすると重複してしまう為、フォローされる側は follow_id とする

上の仕組みによって、 user.followingsでuser が中間テーブル relationships を取得し、
その1つ1つの relationship の follow_id から 自分がフォローしている User 達 を取得する流れができる。


models/user.rbに見やすく色をつけるとこんな感じ。

has_many :relationships
has_many :followings, through: :relationships, source: :follow

has_many :reverses_of_relationships, class_name: 'Relationship', foreign_key: 'follow_id'
has_many :followers, through: :reverses_of_relationship, source: :user

Railsのアソシエーションの仕組み

理解するのにものすごく時間がかかったので、まとめておく。

アソシエーションとは

簡単にいうとモデルを参照するためのメソッド。
例は下記に示す。

メソッドはふだん使うときに書くが(当たり前)、アソシエーションは使い道も書き方も決まっていてるので、初めにまとめて書いておく決まり。

アソシエーションを利用するケース

usersとitemsという多対多のテーブルがあり、relationshipsという中間テーブルを使う場合を考える。

models/user.rb

class User < ApplicationRecord
  has_many :relationships
  has_many :items, through: :relationships
end

models/item.rb

class Item < ApplicationRecord
  has_many :relationships
  has_many :users, through: :relationships
end

models/like.rb

class Relationship < ApplicationRecord
  belongs_to :user
  belongs_to :item
end

下記のように使うことができるが、

@user = item.user

この理由は、 models/item.rb

has_many :users, through: :relationships

上記のアソシエーションはもともと以下のようなメソッドを省略したものだから。

def user
  User.find_by(id: Relationship.find(self.id).item_id)
end

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

こんな感じ。