1日1%成長するブログ

毎日成長するために仕事/プライベートで得た学びをアウトプットするブログです

Rails5でローカルではsqlite3で動かしてHerokuではPostgresを使う手順

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '~> 2.13'
  gem 'selenium-webdriver'
  # Use sqlite3 as the database for Active Record
  gem 'sqlite3'
end

group :production do
  gem 'pg'
  gem 'rails_12factor'  
end
default: &default
  adapter: sqlite3
  pool: 5
  timeout: 5000

development:
  <<: *default
  database: db/development.sqlite3

test:
  <<: *default
  database: db/test.sqlite3

production:
  <<: *default
  adapter: postgresql
  database: myapp_production
  username: myapp
  password: <%= ENV['MYAPP_DATABASE_PASSWORD'] %>

ローカル

bundle install
rake db:create
rails s

Heroku

heroku run rails db:migrate
git push heroku master

package.jsonのdevDependenciesとdependenciesの使い分け

devDependencies

  • ライブラリをdev(開発)する時に必要なライブラリを書く
  • 開発時のみに必要で実行時には入らないもの
- テストツール
- ビルドツール
- タスクランナー等
  • dependenciesに含まれていても動作はする。ただバンドルファイルの容量が増えたり用途がわからなくなったりするので分けた方がいい

dependencies

  • ユーザーとしてライブラリを使う時に必要なライブラリを書く

npm install

  • dependenciesとdevDependencies両方がインストールされる

本番環境でnpm installする時

  • dependenciesさえインストールされればいいはず
NODE_ENV=production npm install
  • NODE_ENVにproductionを指定すればdependenciesだけ入る

  • ちなみにHeorokuはデフォルトdependenciesだけインストールされる設定になっている

VSCodeにES6+ReactのESLint設定を行う

1. eslintをプロジェクトのローカルにインストール

yarn add --dev eslint

手軽さからグローバルにインストールする例もありますが、 チームで開発する場合は以下の理由からローカルにインストールした方がいいです

  • プロジェクト毎に別々のバージョンのeslintが利用できる
  • eslintに必要なプラグインも全てグローバルに入れる必要がある
  • eslintやプラグインのバージョンをチームで統一しやすい

2. eslint-config-airbnbをインストール

$ yarn add --dev eslint-config-airbnb eslint-plugin-import eslint-plugin-react eslint-plugin-jsx-a11y babel-eslint

3. VSCodeのESLint連携用のプラグインをインストール

VSCode拡張機能からESLintをインストールします

4. 設定ファイルを用意

{
  "extends": ["airbnb"],
  //ESlint標準のパーサーだとES6の一部記法が認識できずパースエラーになるのでbabel-eslintを使う
  "parser": "babel-eslint",
  "plugins": [
    //Reactのチェックに必要
    "react"
  ],
  "rules": {
  }
}

上記の設定だとかなりキツめの設定なので使ってみて気に入らない所はrulesに追記して行くのがいいでしょう

5. AutoFixを有効にする

{
    "eslint.autoFixOnSave": true
}
  • VSCodeのユーザー設定でeslintの自動修正を有効にします
  • セミコロンの有無やスペース数を自動で直してくれて便利です

ESLintが何か動かないぞ?という時

Parsing errorになっている

  • 必要なプラグインがインストールされていなかったり、パーサーがES6に対応していなかったりしてパースエラーになっている可能性があります
  • ESlintはパースエラーになると、そこから後のチェックはしてくれないです

Rubyの&とRailsの#tryの違い

Ruby&.

  • ruby2.3から追加された新しい演算子
  • Safe Navigation Operator(&.)
10&.to_s # => "10"
nil&.to_s # nil
  • レシーバがnilでない場合にメソッドを呼び出す
  • レシーバがnilの場合はnilを返す

Rails#try

10.try(:to_s) # => "10"
nil.try(:to_s) # => nil
10.try(:to_hoge) # => nil
  • レシーバがメソッドを呼び出せる場合にそのメソッドを呼び出す
  • 呼び出せない場合はnilを返す

&.#tryの違い

  • &.はレシーバがnilじゃないけど呼び出せないメソッドを呼びだそうとした時にNoMethodErrorになります
10.try(:to_hoge) # => nil
10&.to_hoge # => NoMethodError: undefined method `to_hoge' for 10:Integer
hoge = { fuga: 'fuga' }
hoge.try(:hogee) # => nil
hoge&.(:hogee) # => NoMethodError: undefined method

結論

Railsでレシーバがnilではない時にメソッドを呼び出したいという時はtryを使うのが無難

ちなみにそもそも存在しない変数に対してアクセスしようとした時はtryを使ってもNameErrorになります。

aaaaaaaaa.try(:to_s)
NameError: undefined local variable or method `aaaaaaaaa' for main:Object

HerokuにRails5のアプリをデプロイする

Herokuにログインする

$ heroku login

Postgresを使ったアプリを作成

$ rails new myapp --database=postgresql

動作確認用にコントローラーを作っておく

$ rails generate controller welcome
<h2>Hello World</h2>
<p>
  The time is now: <%= Time.now %>
</p>
  • app/views/welcome/index.html.erb
root 'welcome#index'
  • config/routes.rb

rubyのバージョンファイルを作る

rbenv local 2.4.0
ruby "2.4.0"
  • ローカルで使っているrubyと同じバージョン番号をGemfileの最後に書く

Herokuにプッシュする

git remote add heroku https://git.heroku.com/myapp.git
git push heroku master

CarrierwaveでUUIDを使ったファイル名にする方法

UUIDとは

  • Universally (普遍的に) Unique (一意な) IDentifier (識別子) の略
  • 誰でも作れるが世界中で重複しないようになっているID
  • RFC4122という共通の規格が決められている

UUIDの種類

  • UUIDの作成方法には5種類存在する。
1: 時刻とMACアドレスを元にするバージョン
2: DCEセキュリティバージョン
3: バイト列を元にするバージョン。MD5ハッシュを利用
4: ランダム生成するバージョン
5: バイト列を元にするバージョン。SHA-1ハッシュを利用
  • RubyのSecureRandom.uuidはバージョン4
  • 16進数を32桁で乱数生成したもの
  • 340澗(かん)通り。重複はしないと考えていい。

www.slideshare.net

なぜUUIDを使うのか?

  • ファイル名にIDを使っている場合、推測ができてしまい他のユーザーが閲覧できてしまう
  • IDからサービスのユーザー数がバレてしまう

上記のようなニーズからUUIDは使われる

UUIDにする方法

  • xxx_uploader.rbのfilenameメソッドを上書きすればいい
def filename
  "#{SecureRandom.uuid}.#{file.extension}" if original_filename
end

How to: Create random and unique filenames for all versioned files · carrierwaveuploader/carrierwave Wiki · GitHub

公式では以下のような書き方がサンプルにあるが、uuidだけでも十分だし読みやすいのでいいのではと思っている。

def filename
  "#{secure_token}.#{file.extension}" if original_filename.present?
end

protected
def secure_token
  var = :"@#{mounted_as}_secure_token"
  model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.uuid)
end

Rails5でajaxを使っていいね機能を実装する

ルーティングを用意する

resources :diaries do
  resources :diary_comments, only: [:create]
  resources :diary_likes, only: [:create, :destroy]
end
  • diaryが重複するので後でスッキリさせる

モデルを用意する

class Diary < ApplicationRecord
  belongs_to :user
  has_many :diary_likes, dependent: :destroy

  # ユーザーのいいねを取得
  def like(user_id)
    diary_likes.find_by(user_id: user_id)
  end
end

部分テンプレートを用意する

<% if diary.like(user.id) %>
  <button class="btn btn-default btn-xs disabled" type="button">
    <i class="fa fa-thumbs-o-up">いいね済み</i>
  </button>
<% else %>
  <button class="btn btn-default btn-xs" type="button">
    <i class="fa fa-thumbs-o-up">いいね</i>
  </button>
<% end %>
<% else %>

button_toに書き換える

 <%= button_to diary_likes_path(diary), remote: true, class: "btn btn-default btn-xs" do %>
   <i class="fa fa-thumbs-o-up">いいね</i>
 <% end %>
  • button_toは実際にはformとbuttonタグとして出力される。こんな感じ
<form class="button_to" method="post" action="/diaries/1/likes" data-remote="true"
    <button class="btn btn-default btn-xs" type="submit">
      <i class="fa fa-thumbs-o-up">いいね</i>
    </button>
    <input type="hidden" name="authenticity_token" value="xx">
</form>
<% if diary.like(user_id) %>
  <%= button_to diary_diary_like_path(diary, diary.like(user_id)), remote: true, method: :delete, class: "btn btn-default btn-xs" do %>
    <i class="fa fa-thumbs-o-up">いいね済み</i>
  <% end %>
<% else %>
  <%= button_to diary_diary_likes_path(diary), remote: true, class: "btn btn-default btn-xs" do %>
    <i class="fa fa-thumbs-o-up">いいね</i>
  <% end %>
<% end %>
<% else %>

いいねボタンを用意

.like-button{:class => "like-button-#{diary.id}"}
  = render "diary_likes/like", diary: diary, user_id: diary.user_id
  • 日記ごとに部分テンプレート化したいいね!ボタンをrenderする

いいね機能を実装する

def create
  @like = DiaryLike.new(user_id: current_user.id, diary_id: params[:diary_id])
  @like.save
end

def destroy
  @like = DiaryLike.find_by(user_id: current_user.id, diary_id: params[:diary_id])
  @like.destroy
end
  • diary_idしか送られてこない & そのまま保存するわけでもないのでストロングパラメータは不要
  • remote: trueで送っているので、xxx.js.erbが呼ばれる

create.js.erbを作成

$(".like-button-<%= @like.diary_id %>").html("<%= j(render partial: 'like', locals: { diary: @like.diary, user_id: @like.user_id }) %>")

destroy.js.erbを作成

$(".like-button-<%= @like.diary_id %>").html("<%= j(render partial: 'like', locals: { diary: @like.diary, user_id: @like.user_id }) %>")