20200519のRailsに関する記事は21件です。

[rails]deviseのヘルパーメソッドbefore_action :authenticate_user!の使い方

before_action :authenticate_user!について

deviseを簡単に説明すると、ログイン系をやってくれるgemです。

そのdeviseのヘルパーメソッドauthenticate_user!メソッドは、コントローラーにbefore_actionで記載することで、そのコントローラーで行われる処理はログインユーザーのみ実行可能とすることができるメソッドです。

before_action :authenticate_user!の使い方

authenticate_user!メソッドはコントローラーに記載します。

class PostsController < ApplicationController
  before_action :authenticate_user!

  def index
  end

end

このように記載するとposts_controllerでの処理をすることができるのはログインユーザーのみとなります。

一部の処理を未ログインユーザーでも行えるようにする

class PostsController < ApplicationController
  before_action :authenticate_user!, only: [:show]

  def index
  end

  def show
  end

end

このように記載することで、showアクションのみを未ログインユーザーが使用できないようにできます。

間違いなどがありましたらご指摘いただければ幸いです。
最後までご覧いただきありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsでグループ機能(掲示板)を作ってみた

Railsでグループ機能(掲示板風)の作成

<開発環境>
1. ruby 2.6.3
2. Rails 5.1.6
3. AWS Cloud9
4. GitHub
6. sqlite3(develop環境)

設計構想

ユーザーは自由にグループを作成でき、また他のユーザーが作成したグループに所属することもできます。
また、グループに参加したメンバーはグループ内で自由に発言することも可能になります。

この仕組みをテーブルに落とし込むと、
ユーザーとグループは多対多の関係になるので中間テーブルを用いることとします。
そして、ユーザーとグループ内での投稿も多対多の関係となる為、こちらも中間テーブルを用います。
IMG_8162 (1).jpg

ER図はこんな感じになりました。(手書きですいません・・・)

モデル

先ほどのER図は以下のようなアソシエーションとなりました。

group.rb
class Group < ApplicationRecord
  validates :name, presence: true, uniqueness: true

  has_many :group_users
  has_many :users, through: :group_users
  has_many :groupposts
  accepts_nested_attributes_for :group_users
end
group_user.rb
class GroupUser < ApplicationRecord

  belongs_to :group
  belongs_to :user
end
grouppost.rb
class Grouppost < ApplicationRecord
  belongs_to :group
  belongs_to :user
end

コントローラー

groups_controller.rb
class GroupsController < ApplicationController
  def new
    @group = Group.new
    @group.users << current_user
  end

  def create
    if Group.create(group_params)
      redirect_to groups_path, notice: 'グループを作成しました'
    else
      render :new
    end
  end

  def index
    @groups = Group.all.order(updated_at: :desc)
  end

  def show
    @group = Group.find_by(id: params[:id])

    if !@group.users.include?(current_user)
      @group.users << current_user
    end

    @groupposts = Grouppost.where(group_id: @group.id).all
  end

  private
  def group_params
    params.require(:group).permit(:name, :user_id [])
  end

  def grouppost_params
    params.require(:grouppost).permit(:content)
  end

end

基本的な設計はユーザー周りと同じです。

def show
.
.

  if !@group.users.include?(current_user)
   @group.users << current_user
  end
end

このようにグループのリンクを踏んだ人がそのグループに所属できるようにしています。

grouppost_controller.rb
class GrouppostsController < ApplicationController

  def new
    @grouppost = current_user.groupposts.new
    @group = Group.find_by(id: params[:group_id])
  end

  def create
    @group = Group.find_by(id: params[:group_id])
    @grouppost = current_user.groupposts.new(grouppost_params)
    @grouppost.group_id = params[:group_id]
    if @grouppost.save
      redirect_to group_path(@group.id)
    end
  end

  private
    def grouppost_params
      params.require(:grouppost).permit(:content)
    end
end

こちらも以前作成した投稿機能と同じ形にしています。

以上で2chちっくなグループ機能(掲示板風)が完成しました。
今後の課題としては、グループ作成時に鍵をかけることができ、招待制のグループを作ることができるようにしたいと考えています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

devise導入からフラッシュ実装②

最初に行うコマンド

deviseをインストールする

rails g devise:install

userモデルを作る。ユーザー管理に必要なため

rails g devise user

いつもの

$ rails db:migrate

ヘッダーをいじります。新規登録とログインとログアウトのリンクを作る

          = link_to 'ログアウト', destroy_user_session_path, method: :delete, class: 'header__top__userMenu--logout'
        - else
          = link_to '新規登録', new_user_registration_path, class: 'header__top__userMenu--logout'
          = link_to 'ログイン', new_user_session_path, class: 'header__top__userMenu--logout'

未ログインのアクセス制限

before_action :move_to_index, except: [:index]

private
  def move_to_index
    redirect_to action: :index unless user_signed_in?
  end

deviseのコマンドでビューを作る

rails g devise:views

サインアップ時にニックネームを登録

userテーブルにカラムを追加

rails g migration AddNicknameToUsers nickname:string

最大文字数のために

     <div class="field">
        <%= f.label :nickname %> <em>(6 characters maximum)</em><br />
        <%= f.text_field :nickname, autofocus: true, maxlength: "6" %>
      </div>

を追加

ニックネームカラムを送れるようにする
devise_parameter_sanitizerはrequireみたいな意味らしい

application_controller.rb

  before_action :configure_permitted_parameters, if: :devise_controller?

  protected
  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname])
  end

tweetsカラムにuser_idを追加する

rails g migration AddUserIdToTweets user_id:integer

ツイート投稿時にユーザーidを追加

.merge(user_id: current_user.id)

アソシエーションの定義

1,モデルクラスにhas_many ~~sやbelongs_to ~~(単数形)で関係が定義されてる
2,所属する側のテーブルに「所属するモデル名_id(例)tweet_id」というカラムがある

has_many :tweets
belongs_to :user

マイページの作成

1.マイページのルーティングを記述しよう
2.マイページへのリンクを作成しよう
3.コントローラーとアクションを作成しよう
4.マイページ用のビューファイルを作成しよう

resources :users, only: :show
= link_to current_user.nickname, user_path(current_user.id)

Usersコントローラーを作成

rails g controller users

  def show
    @nickname = current_user.nickname
    @tweets = current_user.tweets
  end

ツイート一覧画面にニックネームを表示
1.投稿者名を表示するようにビューを変更しよう
2.ツイートからユーザー情報を先読みしよう
3.投稿画面のビューを変更しよう
4.投稿時のコントローラーでの処理を変更しよう
includes(:user)を使ってincludesを解消

ログインユーザーのみ編集削除が可能にする
orderメソッドで。

@tweets = Tweet.includes(:user).order('updated_at desc')

ページネーションの実装

kaminariをbundle install
?pageメソッド
paramsの中にpageキーが追加される。引数はparams[:page]
?parメソッド
1ページあたりに表示する件数を指定
?paginateメソッド
ページネーションのリンクを表示するメソッド

@tweets = Tweet.includes(:user).order('updated_atdesc').page(params[:page]).per(5)  
<%= paginate(@tweets) %>

コメント機能

$ rails g model comment

マイグレーションファイルを編集してrails db:migrate
アソシエーションとroutesの設定

$ rails g controller comments

  def create
    Comment.create(comment_params)
  end

  private
  def comment_params
    params.require(:comment).permit(:text).merge(user_id: current_user.id, tweet_id: params[:tweet_id])
  end

詳細ビューにコメント投稿フォームと一覧を変更

   <%= form_with(model: [@tweet, @comment], local: true) do |form| %>
        <%= form.text_area :text, placeholder: "コメントする", rows: "2" %>
        <%= form.submit "SEND" %>

showアクションにコメントを追加(超大事、comment定義してなくて1時間こけた)

  def show
    @comment = Comment.new
    @comments = @tweet.comments.includes(:user)
  end

検索機能の実装(2種類あり)

①collectionを使う

tweetにネストしてルーティングの設定

  collection do
      get 'search'
    end

form_withで検索するフォームを作る

tweetモデルに検索するメソッドを作成

  def self.search(search)
    if search
      Tweet.where('text LIKE(?)', "%#{search}%")
    else
      Tweet.all
    end
  end

tweetsコントローラにsearchアクションを定義

  def search
    @tweets = Tweet.search(params[:keyword])
  end

検索結果のビューを表示

  <% @tweets.each do |tweet| %>
    <%= render partial: "tweet", locals: { tweet: tweet } %>
  <% end %>

②namespaceを利用した方式(⑦つのアクションのみ使用)
searchesコントローラーを作成

$ rails g controller tweets::searches

namespaceのルートを追加する
resouece :tweetsより上に書くこと。
ルートの順番の問題。tweetsのルートが先に読まれておかしくなるから

  namespace :tweets do
    resources :searches, only: :index
  end

検索フォームを追加する

<%= form_with(url: tweets_searches_path, local: true, method: :get, class: "search-form") do |form| %>
 <%= form.text_field :keyword, placeholder: "投稿を検索する", class: "search-input" %>
  <%= form.submit "検索", class: "search-btn" %>

searchコントローラーにindexアクションを追加する

  def index
    @tweets = Tweet.search(params[:keyword])
  end

tweetモデルに検索するメソッドを作成

  def self.search(search)
    if search
      Tweet.where('text LIKE(?)', "%#{search}%")
    else
      Tweet.all
    end
  end

検索結果フォームを追加

 <% @tweets.each do |tweet| %>
    <%= render partial: "tweets/tweet", locals: { tweet: tweet } %>
  <% end %>

フラッシュメッセージの実装

①フラッシュメッセージが表示されるようにする
②フラッシュメッセージのスタイリングを行う
③フラッシュメッセージを日本語化する

app/views/layouts/_notifications.html.haml

.notification
  - flash.each do |key, value|
    = content_tag :div, value, class: key
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Railsチュートリアル】第1章にあるGitのこと


はじめに

この記事ではRails チュートリアルの第1章
で使用されているGitの使い方について
まとめた記事です。
ベストプラクティスや間違いがあれば
チュートリアルをやりながら書き直していく予定です。

Git

言わずと知れたVersion管理ツール
ちなみにGit HubとGitは別物
よく勘違いされているのであらかじめ書いておきます。

Git : 技術の名前、コマンド
Git Hub : Gitを使ったサービス

ザックリいうとそんな感じです。

チュートリアル 第1章

チュートリアル上で使われてるAmazonのクラウドIDE
Cloud9もそうですが、まぁ日頃使っていないので
使い方を忘れるんですよね。。。
それと同じようにGitも業務に取り入れていない状態で
案の定忘れていました。

今後、職場に導入することも考えて
自分なりのリファレンスをどこかで作りたいな。。。

今回はそんな忘れっぽい私のような人に贈る内容です。

設定コマンド

インストールをするときに欠かせない設定コマンド
社内のそれも外の世界とつながっていない場合でも
これは入れるのだろうか。。。

git config --global user.name [username]
git config --global [mailaddress]

最低限知っておきたい Gitコマンド

$ git init
$ git add -a
$ git status
$ git commit -am "命令系のコメント"

git addの-aオプションは
作業ツリーのファイルをすべてステージするという意味
チュートリアルでは注意して使うように書いてある。
「変更する必要のないファイルも
一緒にステージングしてしまうから」という意味で書いてるのかな

といっても中途半端にあらゆるファイルに変更を加えるのにはリスクがある。
そう思うのは一人で開発しているからかな。

ちなみに初めて知りましたがGitのコメントは命令形
(チュートリアルでは体言止めと表記)が良いみたいです。

筆者は日頃から体言止めで書いているので
ここは当たり前のようにcommitしてました。

Git初心者から脱却したいなら

リポジトリやバックアップについて勉強しよう。
Gitではブランチを使うことで
変更履歴を複数持たせることができる。

文字通どおり枝分かれさせることができるので

オリジナルファイルを複数持つことなく
変更履歴A 変更履歴B といった感じに
変更履歴のみを管理することができる。

何がすごいってオリジナルファイルを
2コ3コと持たなくてよいということ

よくファイル名で
ファイル名_YYYYMMDD-0.txtやファイル名_YYYYMMDD-1.txtといった
ファイルを目にすることだろう。

どれが新しいのかわからない
そういった面倒な版数管理をする必要がないのだ。

また、どうしても
2重管理したいということであれば
別のサーバ上にリポジトリを作成することができる。

これを

「リモートリポジトリを作成する」

という。

リモートリポジトリは
まず、リモートにリポジトリを
登録することから始まり
プッシュすることで終わる。

厳密にはSSHの鍵が必要だとかどうとかあるけど今回は脱線するので
もっと知りたい人は調べてください。

' 現在、スイッチしているブランチ名を参照する
$ git checkout

'リモートリポジトリの操作(ここではmasterブランチにプッシュ)
$ git remote add origin リポジトリURL
$ git push origin master

' 枝分かれtestの作成
$ git checkout -b test

' 枝分かれtestとmasterの結合(masterにスイッチ後)
$ git merge test

' 全ての枝分かれを表示
$ git branch

' コミットログの表示(変更履歴が多い場合はこのまま打つのは得策ではないかも)
$ git log

'任意の変更箇所まで戻る(もしかすると、ベストプラクティスがあるかも)
$git reset ハッシュ値

出力メッセージの英語翻訳

あと、チュートリアルの最中に気になった文言

単語 日本語
On branch master 親ブランチを参照しています
No commits yet まだコミットされていません
Changes to be committed コミットされる変更
Changes not staged for commit 変更がステージングされていません
nothing to commit, working tree clean 作業ツリーにコミットするものは何もありません
discard changes in working directory 作業ディレクトリの変更を無視する

おわり

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails テスト

前提

本日学んだことを書いていきます。

本題

単体テスト

RSpecをインストール

#省略

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]

  gem 'rspec-rails', '~> 3.8' #追記
  gem 'factory_bot_rails', '~> 5.0' #追記

end

# 省略
$ bundle install

Rspec・FactoryBotがインストールされているか確認

$ bundle exec rspec --version
$ bundle exec gem list | grep factory_bot_rails

アプリのプロジェクトにRSpecを導入

$ rails g rspec:install

テスト用のDBを最新の構成にする

$ rails db:migrate:reset RAILS_ENV=test

テスト用のDBとは自動テストを実行するときに利用する専用のDB
例えば自動テストで「全データが消えること」を確認するとき、開発中に利用しているデータが消えてしまっては困る
開発中のデータと自動テスト用のデータを分離して考えることで、双方が干渉しないようになる

モデルのテストコードを追加

$ rails g rspec:model モデル名

テスト実行コマンド

$ bundle exec rspec

RSpecでテストケースを識別するための3つのキーワード

・describe
正常系の機能
大分類のようなイメージで何についてテストをするかを記述する

・context
回答する
中分類のようなイメージで状況を記述する

・it
正しく登録できること
個々のテストケースを表し、期待する振る舞いを記述する

enquete = FoodEnquete.new(
          name: '田中 太郎',
          mail: 'taro.tanaka@example.com',
          age: 25,
          food_id: 2,
          score: 3,
          request: 'おいしかったです。',
          present_id: 1
        )

# バリデーションが通ること(バリデーションエラーが無いこと)を検証
expect(enquete).to be_valid

※Rails 5.2以降のRSpecではテストコードの実行が終了すると、DBの状態を元に戻してくれる

$ bundle exec rspec spec/models/〜.rb
.

Finished in 0.03355 seconds (files took 2.24 seconds to load)
1 example, 0 failures

実行結果の1行目に記号やアルファベットで結果が表示される

.成功 テストコードの想定通りに動作した
F失敗 テストコードの想定通りに操作しなかった

# true/falseのような真偽値を検証する時、be_truthy/be_falseyをマッチャーとする
expect(new_enquete.save).to be_falsey
_enquetes.rb
FactoryBot.define do
  factory :food_enquete do

    name { '田中 太郎' }
    mail { 'taro.tanaka@example.com' }
    age { 25 }
    food_id { 2 }
    score { 3 }
    request { 'おいしかったです。' }
    present_id { 1 }

  end
end
_enquete_spec.rb
# FactoryBot.build(クラス名)
enquete = FactoryBot.build(:food_enquete)

FactoryBot.create(クラス名)を呼出すと「田中 太郎」のテストデータが作成、さらにテストデータがDBへ保存される
つまり、.saveが不要になる

FactoryBot.create(:food_enquete)

FactoryBot.build(クラス名, 上書きしたい項目: XX)を呼出すと「田中 太郎」のテストデータが作成される
ここでは「お召し上がりになった料理」「満足度」「希望するプレゼント」「ご意見・ご要望」を上書きしている

re_enquete_tanaka = FactoryBot.build(:food_enquete, food_id: 0, score: 1, present_id: 0, request: "スープがぬるかった")
_enquete_spec.rb
# このテストコードでは、各テストケースの前処理として「田中 太郎」のテストデータを作成する
before do
  FactoryBot.create(:food_enquete_tanaka)
end
_enquete_spec.rb
# インスタンスを共通化してテストデータを作成
let(:new_enquete) { FoodEnquete.new }

共通メソッドのテストコードのファイルを認識させる
モデルのテストコードを書くためのファイルはrails g rspec:modelで準備したが、共通メソッドは手動で用意する
RSpecが手動で作成するテストコードのファイルを認識するように設定を修正する
以下のテストコードのモデルのインスタンスを共通化
まずは修正前のテストコードを確認

spec/rails_helper.rb
# 省略

# コメントアウトを解除する
# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

# 省略

共通メソッドのテストコードのファイルを追加
spec/support/concerns/common_modules.rbにテストコードを追加

共通のテストコードを定義するときはshared_examplesを利用し、その中にテストコードを書く

_enquete_spec.rb
# 共通のテストコードを呼出すときはit_behaves_likeを利用
it_behaves_like '価格の表示'

統合テスト

capybara
統合テスト(E2E)のフレームワーク
Rspecと連携してWEBブラウザの自動操作を支援する

selenium-webdriver
capybaraを通じてWEBブラウザを自動で操作する

webdrivers
GoogleChromeを操作するドライバ

Gemfile.
group :test do
  gem 'capybara', '~> 3.28'
  gem 'selenium-webdriver', '~> 3.142'
  gem 'webdrivers', '~> 4.1'
end
terminal.
$ bundle install

インストールされているか確認

$ bundle exec gem list | grep -e capybara -e selenium-webdriver  -e webdrivers
capybara (3.32.2)
selenium-webdriver (3.142.7)
webdrivers (4.3.0)

spec_helper.rbの修正

spec_helper.rb
# 省略

require 'capybara/rspec' #追記

RSpec.configure do |config|

  # ブラウザにChromeを指定(以下追記)
  config.before(:each, type: :system) do
    driven_by :selenium, using: :chrome, screen_size: [1280, 960]
  end


  # 省略

end

※Chromeを1280px × 960pxのウィンドウサイズで開いて操作

追記
--skip-test
デフォルトのテストフレームワークを作成しない

--skip-coffee
CoffeeSprictを使用しない

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerでの環境構築(Rails)超入門3 ~MySQLを立ち上げる~

これまで

Dockerでの環境構築(Rails)超入門1
Dockerでの環境構築(Rails)超入門2 ~Dockerfileの設定~

やること

前々回立ち上げたRailsは初期設定で「sqlite3」に接続している
今回はMySQLサーバーを立ち上げ、複数コンテナを動作させる

手順

  1. MySQLサーバーを立ち上げる
  2. Railsのプロジェクトを書き換える

実践

  1. MySQLサーバーを立ち上げる

1) docker-compose.yamlの編集

docker-compose.yaml
version: '3'
services:
  mysql:
    image: mysql:8.0.20
    command: --default-authentication-plugin=mysql_native_password
    volumes:
       - "./mysql-data:/var/lib/mysql"
    environment:
      MYSQL_ROOT_PASSWORD: root
  app:
    build: .
    volumes:
      - ".:/app"
    ports:
      - "3000:3000"
    tty: true
    depends_on:
      - mysql

2)「mysql-data」フォルダをappと同じ階層に作成

3) Dockerfileに以下を追加

Dockerfile
FROM ruby:2.6.6-stretch
RUN gem rails install
RUN apt-get update && \
    apt-get install -y node.js *mysql-client*

COPY Gemfile/Gemfile
COPY Gemfile.lock/Gemfile.lock

RUN bundle install

4) Mysqlに接続する

$ docker-compose up --build
$ docker exec -it practice_app_1 /bin/bash
/# mysql -u root -proot -h mysql
> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+

2 . Railsのプロジェクトを書き換える
・Gemfileを以下に編集

Gemfile
(省略)
gem 'sqlite3'→ gem *'mysql2'
(省略)

・config/database.yamlを編集

config/database.yaml
default: &default
  adapter: mysql2
  enconding: utf8
  username: root
  password: root
  host: mysql
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development:
  <<: *default
  database: practice_development

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: practice_test

production:
  <<: *default
  database: practice_production

・app/#に移動してrake db:createでデータベースを作成

・再度Mysqlに接続してデータベースが作成されていることを確認

+----------------------+
| Database             |
+----------------------+
| information_schema   |
| mysql                |
| performance_schema   |
| practice_development |
| practice_test        |
| sys                  |
+----------------------+

最後にrails s -b 0.0.0.0でローカルに接続すれば開発環境の構築はOK!

補足

  1. 別のコマンドでコンテナに入る

今まで$ docker exec -it pracitce /bin/bashでコンテナに入っていたが、「docker-compose.yaml」が入っているファイルがあれば、

$ docker-compose exec コンテナ名(app)/bin/bash

2 . コンテナに入ったあと、自動的にappに移動したい
1) docker-compose.yamlの編集

docker-compose.yaml
version: '3'
services:
  mysql:
    image: mysql:8.0.20
    command: --default-authentication-plugin=mysql_native_password
    volumes:
       - "./mysql-data:/var/lib/mysql"
    environment:
      MYSQL_ROOT_PASSWORD: root
  app:
    build: .
    volumes:
      - ".:/app"
    ports:
      - "3000:3000"
    tty: true
    depends_on:
      - mysql
#以下を追加
    *working_dir: "/app"*

これでdocker exec compose app /bin/bashの後、自動的にapp/#に移動している

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Day17 #Railsデバッグ方法

デバッグのツールについて

  • binding.pryを追記することで、エラー内容のヒントになる。
app/controllers/posts_controller.rb
  def create
    binding.pry
    Post.create(post_params)
    redirect_to root_path
  end
[2] pry(#<PostsController>)> post_params
Unpermitted parameter: :titles
  User Load (0.6ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 2 ORDER BY `users`.`id` ASC LIMIT 1
  ↳ app/controllers/posts_controller.rb:38
=> <ActionController::Parameters {"content"=>"牙通牙", "user_id"=>2} permitted: true>

#unpermittedの内容を教えてくれる。
  • Rails処理の流れ
    image.png

  • Table間のER図(Entity-Relationship Diagram)

image.png
 ・ユーザー1に対して、ツイート、コメントは複数。
 ・1ツイートに対して、コメントは複数。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

atomのerbでrailsのコードが上手く反応してくれない

atom.png

ruby on railsを勉強している初心者です。progateのruby on rails のすべて終了後、自分でコードを書いているのですが、index.html.erbに<% >を追加したところ、以下のhtmlコードはすべて反応しなくなりました。このような時はどのような対応をすればよいでしょうか。atomでコードを書いています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

大学生Railsエンジニアが、1人でWebとアプリをリリースするまでの3ヶ月間の話

Qiita初投稿なのでお手柔らかに。

自分は大学生ながら都内のスタートアップでエンジニアをしています。

普段はRailsでのWebサービス開発がメインで、SwiftもKotlinも触ったことすらありませんでした。

経験ゼロのReact NativeとExpoでリリースまでに得た知見を残していきます。

開発したサービス

top_image.jpg
SportsLab | スポーツをより深く楽しめる新しいメディア

コメントと共にスポーツのニュースを読めるサービスです。

Webとアプリの両方を1人で開発・運用しています。スタートアップあるあるですね笑。

使用技術

ざっくり使用している技術を紹介します。

Web: Rails 5.0, jQuery

アプリ: React Native, Expo, (TypeScript)

インフラ: ElasticBeanstalk(AWS)

認証: Firebase Auth

CI: Circle CI

スタートアップではよくある感じの技術構成だと思います。

Railsのエコシステムや周辺ライブラリの豊富さには助けられました。

アプリについては僕がSwiftやKotlinをかけないため泣く泣くReact Nativeを採用しました。

開発スケジュールを振り返る

開発の時間軸としては

12/24
記念すべきfirst commit
1/1
年明けにEC2にデプロイ
1/2
ドメインを取得してWeb版をローンチ
1/4
アプリを開発開始
2/1
Appleに初めて申請
2/13
朝起きたらアプリが公開されてました!
2/20
安定版がストアで公開される

一人で開発して良かった事

今までのJSの経験を生かす事が出来た

React Native & Expoを採用した事でSwift, Kotlinを勉強する事なく純粋にJavaScript(TypeScript)を書くだけでモバイルアプリを開発できた、というのが一番ですね。

OTAアップデートで簡単に更新できる

また、ExpoにはOTAアップデート機能があるのでそちらを使えばストアに申請する事なくユーザのアプリを強制的にアップデートできるのも魅力の1つです。

デザインについても Native BaseReact Native Elements といったUIライブラリが揃っているのでCSSをほとんど書く事なく開発を進められたのも大きかったです。

一人で開発して辛かった事

気軽にストア申請できない

ユーザによって使うアプリのバージョンがまちまちになってしまうので、API側でルーティングの変更などの仕様を変えられないのが辛いです。

TypeScript難しい?

React Nativeは完全に独学で、今も付け焼き刃の状態でコードを書いているのでアプリの挙動が不安定なのがネックでした。

少しでもバグを減らすためにTypeScriptを導入しましたが、動的型付けであるRubyしか書いてこなかったので、そもそも型ってなんですか?というレベルで、TypeScriptの恩恵を十分に受けられてないです。

これから勉強していきます!

ファイルの肥大化

useEffectで通信処理をViewにベタガキしてるのでファイルが巨大化してカオスofカオス。

Reduxや, Redux-sageなどのミドルウェアの学習と導入が待たれる。。。(そう言えば、つい最近FaceBookが新しい状態管理ライブラリを出してきましたね!)

申請が通らない!!!

最初App Storeの申請で数週間悩まされました。理由はこいつでした。

4.2 minimum functionality We found that the usefulness of your app is limited by the minimal features it includes.

どうも調べたところお前の作ったアプリは⚪️ミだからアウト!っていう意味らしいです。

Push通知実装したり、記事の検索機能つけたりいろいろしたのですがダメでした。

ではどうやって通したかというと・・・

ウォークスルーを実装しました!!!

スクリーンショット 2020-05-19 01.55.07.png

スクリーンショット 2020-05-19 01.54.25.png

それだけです。

正直なんで通ったのか分からないです。

どうやらアプリでないと得られないUXを訴求したのが良かったっぽいです。

同じ理由でリジェクトされてる人が一人でも多く救われますように。

ExpoをアップグレードしたらFacebookでログインできなくなった

ふと朝起きると同僚からSlackで同僚からメンションが来ていました。

Facebookでログインしようとするとアプリがクラッシュするんだけど

報告を受けた瞬間、顔面蒼白になりました。

急いで調査を開始するも、全く原因を特定できませんでした。

焦りを加速させたのはSentry経由でSlackにエラー通知も流れてこないという事でした。

つまり具体的にどこのコードでクラッシュしてるのか検討がつかない。

公式ドキュメント通りに実装してるし、どこが悪いのか悩む日々。

分からなさすぎてExpoにIssueまで立てちゃう始末。

Problem login with Facebook and Firebase

そのあともう一度Expo37のchangelogを調べていくと・・・ありました!!!

https://github.com/expo/expo/pull/7931/files

読んでいくと、途中にこんな記述が。

- In the Expo Client, all of your Facebook API calls will be made with Expo's Facebook App ID. This means you will not see any related ad info in your Facebook developer page while running your project in the Expo Client.
- To use your app's own Facebook App ID (and thus see any related ad info in your Facebook developer page), you'll need to [build a standalone app](../../distribution/building-standalone-apps/).

どうやら開発環境ではFacebookログインはできなくなったようです。

なのでビルドして動作確認する必要があるようでした。

ちなみに僕はこれに気付かず

Possible Unhandled Promise Rejection (id: 0):
[Error: Unsuccessful debug_token response from Facebook: {"error":{"message":"(#100) The App_id in the input_token did not match the Viewing App","type":"OAuthException","code":100,"fbtrace_id":"********"}}]

というエラーを3日間眺める羽目になりました。。。

1人でRailsでAPIを作った感想

JSONの整形にはActive Model Serializerが神

デフォルトでインストールされてるjbuilderは評判悪そうなのでActive Model Serializerを採用しました。

スター数も多いし大丈夫そうって思ってた矢先、更新が止まるという。。。

しばらくはこれで行く予定です。

Netflixが出してるfast_jsonapiっていうのが来てるらしいのですが学習コストが高そうなので断念しました。

Active Moel SerializerはActive Recordベースでかけるため直感的ですがSerializer側に値を渡す時のやり方が分からなくて最初苦労しました。

検索してもヒットするのが古いバージョンの記事ばかりで諦めかけていた、そのとき!

ActiveModelSerializers(0.10系)のインスタンス生成時に引数を渡してSerializerクラス内で使う方法

こちらの記事を発見しました。これに全て書いてあります。神!!!

重い腰を上げてFat Controllerを解決

あと初心者Railsエンジニアあるあるですね。

これどうしようかつい最近まで悩んでいたのですが

実務で学んだRailsの設計・リファクタリング

こちらを参考にさせて頂きました。

POROでサービスクラス(上記の記事ではWorkflows)を作ってそこに切り出そうというアプローチです。

あとControllerを分けてCRUDしかメソッドが作成されないようにする。

試してみましたがいい感じです。自分の書き方が合ってるか自信ありませんが。。。

突然のDockerの導入

実はWeb版のCSSは弊社のCEOが自ら書いています。

で、そのCEOのPCで環境構築しようとしたらなぜか環境構築で詰まりまして。。。

試行錯誤した結果、初めてDockerなるものを導入しました。

と言っても、開発環境でDockerfile書いただけです。

本番環境でのECSやFargateを使ったDockerの運用はリリース後のお楽しみという事で・・・。

Docker for Mac予想以上に重かった!

PCは高いやつを買おうと心に決めました。

こんな感じです!

最後に

今も一人でアプリ、バックエンド、インフラまで面倒見てます!
一緒にコードかきたい方は僕のTwitterアカウントまでDMどうぞ!!!
https://twitter.com/Katsukiniwa

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】gem devise インストール時の流れ

はじめに

Qiita初投稿です。
拙い文章で恐縮ですが、ご一読いただけますと幸いです。
よろしくお願いします。

ユーザー管理機能のためのgem deviseをインストール

1. Gemfileの最終行に以下のように追記

Gemfile
# 省略
gem 'devise'  # 最終行に追記してください

2. ターミナルで bundle install を実行

ターミナル
$ bundle install

3. Gemfile.lockで devise がインストールされたか確認

Gemfile.lock
# 省略
devise

deviseの適用

インストールが完了したら、devise専用のコマンドを利用して設定ファイルを作成

4. ターミナルで rails g devise:install を実行

ターミナル
# deviseの設定ファイルを作成
$ rails g devise:install

新規作成されるファイル

  • config/initializers/devise.rb
  • config/locales/devise.en.yml

5. ターミナルで rails g devise user を実行

ターミナル
# deviseコマンドでモデルを作成
$ rails g devise user

新規作成されるファイル

  • app/models/user.rb
  • db/migrate/20XXXXXXXXXXXX_devise_create_users.rb
  • test/fixtures/users.yml
  • test/models/user_test.rb

また、config/routes.rbにdevise_for :usersの記述が自動的に追記されます。

config/routes.rb
Rails.application.routes.draw do
  devise_for :users
# 省略

6. ターミナルで rails db:migrate を実行

ターミナル
# 作成されたマイグレーションファイルを実行
$ rails db:migrate

7. ターミナルで rails g devise:views を実行

ターミナル
# devise用のビューを作成
$ rails g devise:views

新規作成されるファイル

  • app/views/devise/shared
  • app/views/devise/shared/_error_messages.html.erb
  • app/views/devise/shared/_links.html.erb
  • app/views/devise/confirmations
  • app/views/devise/confirmations/new.html.erb
  • app/views/devise/passwords
  • app/views/devise/passwords/edit.html.erb
  • app/views/devise/passwords/new.html.erb
  • app/views/devise/registrations
  • app/views/devise/registrations/edit.html.erb
  • app/views/devise/registrations/new.html.erb
  • app/views/devise/sessions
  • app/views/devise/sessions/new.html.erb
  • app/views/devise/unlocks
  • app/views/devise/unlocks/new.html.erb
  • app/views/devise/mailer
  • app/views/devise/mailer/confirmation_instructions.html.erb
  • app/views/devise/mailer/email_changed.html.erb
  • app/views/devise/mailer/password_change.html.erb
  • app/views/devise/mailer/reset_password_instructions.html.erb
  • app/views/devise/mailer/unlock_instructions.html.erb

以上、gem devise のインストール時の流れを説明させて頂きました。
少しでも多くの方の参考になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

wicked pdfは、windowsでは動きません!

wicked pdfをリファレンス通りに実行し、
ネットの記事を英語を含め読みまくっても、全く動かず、
エラーを吐きまくっていた。

なんでやねんと、検索してあたった記事がこれ。
https://github.com/mileszs/wicked_pdf/issues/693

以前はwindowsも対応していたらしいが、今はしていないらしい。

このコメントによって、公式のread meからwindows対応の文字が消された模様。

要するに、
wicked pdfは、windowsでは動きません!(2020.05.19時点)

local環境だけでよければ可能なもよう。
windowsのc:にダウンロードして、
rails アプリのpathの先を、wicket PDFのインストール先に設定すると
動くみたい。

だが、某は本番アプリで使いたいので、意味がなし!!!
ってなことで、windowsユーザーのプログラマーさんは、
無駄な時間を使わないようにお気を付けください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HerokuデプロイでMySQLのConnectionErrorが出たときの対処

状況

heroku上にアプリを作成するまでは完了していて、URLにアクセスしたら「Welcome to your new app!」と出て、
herokuへgitでプッシュまでしたのですが、最後にデータベースを作成しようとしました。しかし、ここでConnectionError。

$ heroku run rails db:migrate
:1033:in `retrieve_connection'
/app/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.4.3/lib/active_record/connection_handling.rb:118:in `retrieve_connection'
/app/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.4.3/lib/active_record/connection_handling.rb:90:in `connection'
/app/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.4.3/lib/active_record/tasks/database_tasks.rb:172:in `migrate'
/app/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.4.3/lib/active_record/railties/databases.rake:60:in `block (2 levels) in <top (required)>'
/app/vendor/bundle/ruby/2.5.0/gems/railties-5.2.4.3/lib/rails/commands/rake/rake_command.rb:23:in `block in perform'
/app/vendor/bundle/ruby/2.5.0/gems/railties-5.2.4.3/lib/rails/commands/rake/rake_command.rb:20:in `perform'
/app/vendor/bundle/ruby/2.5.0/gems/railties-5.2.4.3/lib/rails/command.rb:48:in `invoke'
/app/vendor/bundle/ruby/2.5.0/gems/railties-5.2.4.3/lib/rails/commands.rb:18:in `<top (required)>'
/app/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
/app/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
/app/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
/app/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
/app/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
/app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:291:in `block in require'
/app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:257:in `load_dependency'
/app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:291:in `require'
/app/bin/rails:9:in `<main>'
Tasks: TOP => db:migrate
(See full trace by running task with --trace)

解決した方法

色々なサイトをみているうちに、DATABASE_URLが関係あるということがわかったので、見てみる。

$ heroku config | grep CLEARDB_DATABASE_URL
=> CLEARDB_DATABASE_URL=mysql://~省略

「mysql://」を「mysql2://」に変える必要がある!
gemではmysql2を使っていたので対応させる必要があった。

なので上書きします

$ heroku config:set DATABASE_URL=mysql2://~省略
Setting DATABASE_URL and restarting  アプリ名... done, v9
DATABASE_URL: mysql2://~省略

これで上書きされました。
そして再度データベース作成をしてみます

$ heroku run rails db:migrate
...省略
Migrating to CreateIntros (20200518235947)
== 20200518235947 CreateIntros: migrating =====================================
-- create_table(:intros)
   (10.1ms)  CREATE TABLE `intros` (`id` bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL)
   -> 0.0106s
== 20200518235947 CreateIntros: migrated (0.0106s) ============================

これで正常にDBが作成され、公開できます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby public protected private

public

デフォルトの設定、クラスの外部からでも呼び出せる。

protected

外部から隠す
そのメソッドを定義したクラス自身と、そのサブクラスのインスタンスメソッドからレシーバ付きで呼び出せる

private

外部から隠す
クラスの内部とサブクラスでのみ使用できるメソッド
レシーバを指定して呼び出すことはできない

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerコンテナ実行時に、You must use Bundler 2 or greater with this lockfile.

dockerコンテナ上でdocker-compose exec app rails consoleコマンドでrailsのコンソールを立ち上げようとしたところ、You must use Bundler 2 or greater with this lockfile.と出てしまったときの対処法。

[メモ]

docker-compose exec app gem install bundler
docker-compose exec app bundle install

以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsデータベース基礎

テーブル

表形式の収納場所でデータベースの中にいくつも作成することができる。
データベースが存在していても、このテーブルがなければデータを保存することはできません。

レコードとカラム

テーブルは表形式になっていますが、その横一行のことをレコードと言います。また。縦一列のことをカラムと言います。
テーブルのレコードを特定するために、idというカラムが用意されています。

DOA

DOAとはデータ中心アプローチのことで、プログラムよりも前にデータ設計を先に行う方法のことです。
サービス開発がより効率的になることが期待されます。

データベース設計

手順としては
1.テーブルの抽出
 途中でテーブルを追加すると関係性を見直さなければいけなくなる。
 予約語は使わないように。

2.テーブルの定義
 各テーブルが持つカラムを決める。途中で追加するとコードを書き直したり、ビューを変更しなくてはいけない場合が出てくるからです。
 予約語は使わないように。

3.テーブル構造を整理
 同じカラム名を同じテーブル内に作ってはいけません。
そのような場合は別でテーブルを作りましょう。
EX)同じテーブルに imageカラム、 image2カラムなど。この場合はimageテーブルを作る。

4.ER図の作成
テーブル同士の関係をわかりやすく表した図。
IE表記法という書き方で書く。

モデル

モデルの命名規則
モデルクラス名は先頭は大文字の単数形
モデルクラスのファイル名は、先頭小文字の単数形
テーブル名は先頭小文字の複数形
EX)Animal モデルクラス名
animal モデルクラスのファイル名
animals テーブル名

これらモデルファイルとテーブルを作成するコマンドがあります。
それが rails g modelコマンドです。このコマンドの後に作成したいモデルクラスの名前を全て小文字で続けます。
EX)rails g model animal

このコマンドを叩くとテーブルが作成されると言いましたが、テーブルの設計図ができているだけでテーブル自体はまだできていません。テーブルの設計図のことをマイグレーションファイルと言います。

マイグレーションファイル

changeメソッドを使って作成するカラムを指定できます。

カラムの型
integer 数字 idなど
string 少なめの文字 ユーザー名、パスワード
text 多めの文字 投稿文
boolean 真か偽か 真偽フラグ
datetime 日付と時刻 作成日時、更新日時

マイグレーションファイルの設定

2020XXXXXXXXXXXXXXXX_create_XXXX.rb
class CreateXxxxxx < ActiveRecord::Migration[5.2]
  def change
    create_table :xxxxs do |t|
      t.string    :name
      t.text      :text
      t.text      :image
      t.timestamps null: false
    end
  end
end

マイグレーションファイルの実行

rake db:migrate
このコマンドを行うと、ファイルが更新されます。
スキーマファイルには最新のマイグレーションファイルのバージョンが記録される。

schema_migrationsはデータベースの変更履歴のようなもので、どのマイグレーションファイルまでが実行されているかが記録されています。

マイグレーションファイルはschema_migrationsと齟齬が生じる恐れがあるので消してはいけません。
マイグレーションファイルは一度rake db:migrateで実行してしまうと、中身を書き換えて再実行はできません。
rake db:rollbackでデータベースの状態を最新のmigrationファイルを実行する前に戻せます。

Active Record

Active RecordはRubyのGemの一種です。このGemはRailsにデフォルトでインストールされており、このGemのおかげでモデルとテーブルをつなぎ合わせられています。そのことによりRailsからテーブルのレコードにアクセスできます。
Actice Recordを利用するにはApplicationRecordというクラスを継承する必要があります。
ApplicationRecordを継承することでallメソッド、newメソッド、saveメソッド、findメソッドなどが利用できるようになります。

SQL

データベースに保存されているデータをデータベースに要求するときに使う言語です。
RailsではActive Recordのおかげで簡単にデータを要求することができます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Deviseで認証メールを手動で送信したい

再送方法

Qiita - deviseの確認メールの実装で困ったとき
RubyDoc - Module: Devise::Models::Confirmable

ほぼここに書いてあるとおりで、Devise参照先も書いてあるとおりなんですがこれです。

User.find(1).send_confirmation_instructions # manually send instructions

この操作をするシチュエーション

  • メーラー側でなんらかの不具合がありメールが送信されなかった
  • Workerが止まってしまいメール送信用のキューをロストした

等が考えられます。
結構運用中に発生しがちで毎回どうやって再送するんだっけ?って調べてたので書きました。

以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録】Rails3ルーティング確認

はじめに

今回はタイトルの通りにRailsにおけるルーティングの確認方法を残します。
触れているシステムのバージョンが古いため、Webブラウザでの確認が出来ずに詰まりました・・・。

環境

Rails 3.0.19
docker-compose version 2

Dockerコンテナからコマンドで確認する

1. サービス用コンテナを起動する

docker-compose up -d

-d:デタッチド・モード: バックグラウンドでコンテナを実行し、新しいコンテナ名を表示

2. railsがインストールされているコンテナに入る(今回はappコンテナ)

docker-compose run app /bin/bash

dockerやRailsにバージョンによって、コマンドが変わります。
直近で多く見られるコマンドは以下の通り。

docker-compose exec app bash

3. ルーティング確認コマンドを実行する

[root@[コンテナID] trunk]# bundle exec rake routes

上記コマンドですと全件出力されてしまい見づらいため、grepコマンドを利用すると見やすいです。

[root@[コンテナID] trunk]# bundle exec rake routes | grep [絞り込みたい文字列]

参考

Docker ドキュメント日本語化プロジェクト
docker-composeコマンド

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails で HTTP DELETE メソッドを使える条件

はじめに

Rails では RESTful な設計とするために各種 HTTP リクエストメソッドを使い分けていて、config/routes.rb では当たり前に DELETE メソッドを定義すると思います。

が、このルーティング、正しく機能するためには条件があります。その条件を満たしていないと正しく動作しません。

結論

jquery-ujs.js もしくは rails-ujs.js が読み込まれていることが条件です。これら JavaScript がトリックによって DELETE をエミュレートしています。

トリックの正体と有効/無効

一般的にブラウザでは GET と POST しか使えません。DELETE リクエストは送信されないので、当該ルートが発現することはありません。例えば link_to method: :delete と書いたとしてもブラウザの素の能力では GET が送信されてしまい、最悪誤動作を引き起こします。

そこで前述 JavaScript が上手いことやって「DELETE のつもりでリクエストするんだぜ」というのを伝えることで、DELETE メソッドではないものの DELETE ルートを発現させています。具体的には form オブジェクトを作って「本来の意図は DELETE だよ」というパラメータを添えて POST しています。

そしてこのトリックは普通に rails new すれば自動的にお膳立てされ有効になります。

が、rails new --skip-javascript すると無効になります。JavaScript に依存しているんだから当たり前ですね。そして前述のように link_to method: :delete は GET になってしまうのです。

手作業でトリックを有効にする方法

何らかの事情で rails new --skip-javascript しなければならない場合に手作業でトリックを有効にする方法は、下記です。各ファイルに各行を追記します。

app/assets/javascripts/application.js
//= require rails-ujs
app/views/layouts/application.html.erb
    <%= javascript_include_tag 'application' %>

別法あります

ここまでは前置きです。
ここからが本題です。

JavaScript を使わなくても DELETE ルートを使うことは出来ます。button_to method: :delete です。

JavaScript が生成している form を静的に生成すればいいわけです。

より link_to ぽく

button_to では input type='submit' が1つ(と input type='hidden' が幾つか)の form が生成されますが、class='button_to' になっています。さらに例えば button_to class: :anchor とすると input type='submit' class='anchor' になります。

生成される.html
<form class="button_to" method="post" action="/logout">
  <input type="hidden" name="_method" value="delete" />
  <input type="hidden" name="authenticity_token" value="ナイショ" />
  <input class="anchor" type="submit" value="LOGOUT" />
</form>

なので、下記のような CSS (SCSS) を書いてやれば、機能も見た目も link_to の代わりに使うことが可能です。

app/assets/stylesheets/custom.scss
form.button_to {
  display: inline;

  input.anchor[type='submit'] { |
    border-style: none;
    padding: 0;
    font-size: 1em;
    cursor: pointer;
    background-color: $bg-color;
    color: $link-color;
  }
}

終わりに

もともと --skip-javascript なんて使ってなかったんですが、Rails 6 が Webpacker で yarn や node_modules 必須になってたものの取り急ぎ小さく new したかったので --skip-javascript したら一旦良さそうだったけど logout 出来ず「!?」となって、、、ここに至りました。

静的最高♪

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js+Webpackを導入したRails環境をdockerで構築してみる【part1】

はじめに

学習をする中での備忘録。
やることはタイトル通り。
※注意
これまでバックエンドを1ヶ月半ほど、フロントを3ヶ月半ほど勉強した初学者です。
内容に間違い/不合理な点などある可能性が高いです。
アドバイスや間違いの指摘など頂けると嬉しいです。

ステップ1. dockerイメージの構築からRailsサーバ起動まで

まずはdocker+Railsの環境を整える。
・dockerインストール済み
・dbはmysql
・rails:5.2.3
・ruby:2.4.5
・sprocketsは削除せず、アセットパイプラインを使用

その他詳しい解説などは省く。

それ用のディレクトリを任意の場所に用意

mkdir test

配下に以下の4ファイルを作成

Gemfile
source 'https://rubygems.org'
# バージョンは適宜指定
gem 'rails', '5.2.3'
Dockerfile
FROM ruby:2.4.5
RUN apt-get update -qq && apt-get install -y build-essential nodejs
RUN mkdir /app
WORKDIR /app
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
RUN bundle install
COPY . /app
docker-compose.yml
version: '3'
services:
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/app
    ports:
      - 3000:3000
    depends_on:
      - db
    tty: true
    stdin_open: true
  db:
    image: mysql:5.7
    volumes:
      - db-volume:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: password
volumes:
  db-volume:
Gemfile.lock
# 空ファイル

Railsプロジェクトを作成

/test
docker-compose run web rails new . --force --database=mysql --skip-turbolinks

forceでGemfile等を上書き、turbolinksは使わないのでskip

dockerイメージを再構築

/test
docker-compose build

→Gemのインストールや作成されたRailsプロジェクトのファイル群をコンテナ内に取り込むために必要。

database.ymlを編集

test/config/database.yml
...
default: &default
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: root
  # 以下二行をmysql用に書き換え
  password: password
  host: db

development:
  <<: *default
  database: app_development
...

passwordはMYSQL_ROOT_PASSWORD環境変数の"password"
hostはMySQLサーバのコンテナのサービス名である"db"

コンテナの起動と確認

・コンテナ起動

/test
docker-compose up

起動が確認できたら一旦停止してください。

データベースを作成

/test
docker-compose run web bundle exec rails db:create

ローカルサーバへのアクセス確認

再度コンテナを起動し、ローカルサーバにアクセスする

/test
docker-compose up

http://localhost:3000 へ飛びRailsサーバの起動を確認

ステップ2. Webpack+Vue.jsの導入

Webpackとパッケージのインストール

まずはRailsのルートディレクトリ(test)に/frontendを作成、移動

/test
mkdir frontend
cd frontend

package.jsonを作成(設定はデフォルト値)

/frontend
yarn init

必要なパッケージ達をインストール

/frontend
yarn add vue webpack webpack-cli vue-loader vue-template-compiler css-loader style-loader babel-loader @babel/core @babel/preset-env sass-loader node-sass --save

webpack.config.jsの作成

/frontend/config/development/webpack.config.js
/frontend/config/production/webpack.config.js
の2つを作成。

/frontend
mkdir config
mkdir config/development
mkdir config/production
touch config/development/webpack.config.js
touch config/production/webpack.config.js

エントリーポイントとなるフォルダの作成

/frontend/src/javascripts/entry.jsを作成します。

/frontend
mkdir src
mkdir src/javascripts
touch src/javascripts/entry.js

各種設定ファイルの編集

・development環境用の設定ファイルを編集
※ test/app/assets/javascript配下に出力されるようoutputパスを書き換えてください

/frontend/config/development/webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
    devtool: 'inline-source-map',
    mode: 'development',
    entry: {
        webpack: './src/javascripts/entry.js'
    },
    output: {
        // /app/assets以下に出力するよう、path:のキーを絶対パスで書き換え
        path: 'ここを書き換え/test/app/assets/javascripts',
        filename: '[name].js'
    },
    module: {
    rules: [
        {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader'
        },
        {
            test: /\.vue$/,
            loader: 'vue-loader'
        },
        {
            test: /\.css$/,
            use: ['vue-style-loader', 'css-loader']
        },
        {
            test: /\.scss$/,
            use: [
                'vue-style-loader',
                'css-loader',
                {
                loader: 'sass-loader',
                },
            ],
        }
    ]
    },
    resolve: {
        extensions: ['.js', '.vue'],
        alias: {
            vue$: 'vue/dist/vue.esm.js',
        },
    },
    plugins: [
        new VueLoaderPlugin()
    ]
}

・production環境用の設定ファイルを編集
※ development同様、outputパスを書き換えてください

/frontend/config/production/webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
    mode: 'production',
    entry: {
    webpack: './src/javascripts/entry.js'
    },
    output: {
        // 書き換え
        path: '書き換え/test/app/assets/javascripts',
        filename: '[name].js'
    },
    module: {
        rules: [
        {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
            options: {
            presets: [
                "@babel/preset-env"
            ]
            }
        },
        {
            test: /\.vue$/,
            loader: 'vue-loader'
        },
        {
            test: /\.css$/,
            use: ['vue-style-loader', 'css-loader']
        },
        {
            test: /\.scss$/,
            use: [
            'vue-style-loader',
            'css-loader',
            {
                loader: 'sass-loader',
            },
            ],
        }
        ]
    },
    resolve: {
        extensions: ['.js', '.vue'],
        alias: {
            vue$: 'vue/dist/vue.esm.js',
        },
    },
    plugins: [
        new VueLoaderPlugin()
    ],
}

ビルド用コマンドの追加

frontend/package.jsonに以下を追記。

frontend/package.json
  "scripts": {                    
    "release": "webpack --config config/production/webpack.config.js",             
    "build": "webpack --config config/development/webpack.config.js",              
    "watch": "webpack --watch --config config/development/webpack.config.js"       
  },

ここまででVueを動かす環境と、Webpackを用いてバンドルファイルをビルドする準備が完了。
最後に、動作確認のため以下の作業を行う。

ステップ3. ここまでの動作確認

必要なファイルの準備

適当なコントローラを作る

/frontend
cd ..
docker-compose run web bundle exec rails g controller tests

コントローラとルートを編集

/app/controllers/tests_controller.rb
def index
end
/config/routes.rb
# 追加
resources :tests

ビューを作成

/app/views/tests/index.html.erb
<div id="app">
  <p>{{name}}</p>
</div>

Vueを書く

/frontend/src/javascripts/entry.js
import Vue from 'vue';

document.addEventListener("DOMContentLoaded", function(event) {
  new Vue({
    el: '#app',
    data: {
      name: '動作しています'
    }
  });
});

ビルドの実行

(frontend配下に移動)

/frontend
yarn run build

動作確認

http://localhost:3000/tests にアクセスし、'動作しています'と表示されていれば完了。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】date_selectのクラス指定

最初に

hamlでdate_selectを使用した際クラスを付与できなかったため、クラス指定の仕方を備忘録として残します。

date_selectとは

https://gyazo.com/29cf6b960c4c2391a24e70069a9d8659
こんな感じでドロップダウン形式の生年月日のフォームを自動で生成してくれるのがdate_select。

ちなみにデフォルトだとこんな感じ。
https://gyazo.com/4100a51de9a365f337e142bad0d7507b

最初form_forのselect_boxを使用しており、年、月、日と3つフォームを生成してオプションつけて、データをまとめて保存する様にして、、、と生年月日のフォームを実装するまでに時間がかかったため、
早く知っておきたかった。。(泣)
https://gyazo.com/0bd5ca777d38932db77858cd64f87448
今後はこの便利すぎるdate_selectを使用していきます。

使用方法

collection_select(オブジェクト名, メソッド名, 要素の配列, value属性の項目, テキストの項目 [, オプション or HTML属性 or イベント属性])

手順

1.コードの記載(今回はコントローラーで@adreess変数に空のハッシュを代入済み、カラムはbirthdayを想定します。)

html.haml
= form_for (@address) do |f|
  = f.date_select :birthday

これでフォーム自体は使用できます。

2.オプションの追加

html.haml
= form_for (@address) do |f|
  = f.date_select :birthday, use_month_numbers: true, start_year: 1970, end_year: Time.now.year, prompt:"--"

use_month_numbers: true, → 月の表示を数字に。デフォルトだと英語表記。

start_year: 1970, → 選択肢として何年からスタートするかを指定。デフォルトだと10年前からになる。

end_year: Time.now.year, → 選択肢の終わりの年を現在に指定。

prompt:"--" → セレクトボックスのデフォルトで表示される文字を”ーー”に指定。デフォルトだと現在の日付が適用される。(これ個人的におすすめ!)

3.クラスの指定

html.haml
= form_for (@address) do |f|
  = f.date_select :birthday, use_month_numbers: true, start_year: 1970, end_year: Time.now.year, prompt:"--", class:"main__box__bottom__content__group3__box__barthday__box"

これでクラスの付与完了!と思いきや、cssが反応しない。検証で調べてみるとクラスが指定されていないということがわかった。最初これでなぜクラスが当たらないのか謎でした。
ソースコードを調べてると、option(use_month_numbers:やprompt:)は第3引数に設定し、html_options(classなどのhtml/css)は第4引数に設定するようです。
https://railsdoc.com/form
要は指定する場所が決められているということで、上記の場合だと第3引数でクラス指定をしていることになり反映されなかったのが原因です。

なので第3引数と第4引数を{}で別々に括ると、

html.haml
= form_for (@address) do |f|
  = f.date_select :birthday, {use_month_numbers: true, start_year: 1970, end_year: Time.now.year, prompt:"--"},
{class:"main__box__bottom__content__group3__box__barthday__box"}

これでクラスが付与されました!
ちなみにオプションなし、クラスのみ指定(第4引数のみ指定)したい場合は、

html.haml
= f.date_select :birthday, {}, {class:"main__box__bottom__content__group3__box__barthday__box"}

カラムと第4引数の間に空の{}を設けることによりクラスが付与されます!

参考文献

https://railsdoc.com/form
https://qiita.com/nakanoyoshiki/items/e87a6238f8febbeb208a

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails + Vue.js でページネーション付きのテーブルを簡単作成

概要

  • 業務でページネーション機能を実装したので、ほとんどそのままの構成で手順を紹介
  • 使用した技術はkaminari(Rails)Vuetify(Vue.js)
  • api経由でデータを取得し、ページネーション付きで表示する
  • ソースコード

Railsの開発環境は特に説明しないが、以下の記事を参考に構築しました
Rails 6 + MySQL on Dockerの環境を秒速で構築する

Bookモデルの定義とサンプルデータの作成

今回はデータベースに保存したBookの一覧をapiで取得します
まずはBookモデルを作成しましょう

# マイグレーションファイルの作成
bin/rails g model Book name:string

# マイグレーションを実行し、Booksテーブルを作成
bin/rails db:migrate

db/seeds.rbを編集しサンプルデータを作成

db/seeds.rb
100.times do |n|
  name  = "example-#{n+1}"
  Book.create!(name: name)
end

seedを実行

bin/rails db:seed

これでBookレコードが100件作成されました

kaminariのインストールとBook一覧取得用apiの作成

Bookテーブルからデータを取得する際にkaminariを使用します
kaminariをインストールします
https://github.com/kaminari/kaminari

Gemfile
# kaminariを追記
gem 'kaminari'
# kaminariのインストール。インストール完了後にサーバーを再起動させましょう
bundle

app/controllers/api/books_controller.rbを作成し、Book一覧を返すapiを実装します

app/controllers/api/books_controller.rb
class Api::BooksController < ApplicationController
  def index
    # 表示するページの番号を指定
    page = params[:page] || 1

    # 1ページあたりの表示件数を指定
    per = params[:per] || 10

    # ページネーションで指定レコードを取得
    books = Book.page(page).per(per)

    # ページネーションした時の全ページ数
    total_pages = books.total_pages

    # レスポンスデータの定義
    response = {
      # bookレコードはidとnameフィールドのみ表示する
      books: books.select(:id, :name),
      total_pages: total_pages
    }

    # json形式でレスポンスを返却
    render json: response
  end
end
config/routes.rb
Rails.application.routes.draw do
  # Book一覧取得用のパス
  get '/api/books', to: 'api/books#index'

  # Book一覧表示用のパス
  get '/books', to: 'books#index'
end

http://localhost:3000/api/booksにアクセスすると次のようなjsonが返ってきます

スクリーンショット 2020-05-18 22.22.11.png

Book一覧表示ページの作成

Book一覧表示用のページを作成します
Vuetifyのv-data-tableコンポーネントv-paginationを使い、Axiosでapiを叩きます
※ ここでは面倒を避けるためCDN経由で環境構築をしてあります。適宜ご自身の環境に合わせた環境構築を行なってください

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>AppName</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%# CDNで Vue.js, Vuetify, Axios をインストールする %>
    <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/@mdi/font@4.x/css/materialdesignicons.min.css" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <%# ここも追加 %>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.min.js"></script>
  <body>
    <%= yield %>
  </body>
</html>
app/views/books/index.html.erb
<div id="app">
  <v-app>
    <v-container>
      <h2>Book一覧</h2>
      <%# テーブル作成用コンポーネント %>
      <v-data-table
        :headers="headers"
        :items="items"
        :items-per-page="itemsPerPage"
        hide-default-footer
      />
    </v-container>
    <%# ページネーション表示用コンポーネント %>
    <v-pagination
      v-model="currentPage"
      :length="totalPages"
      <%# ページを変更した時にfetchBooksを呼び出す %>
      @input="fetchBooks"
    />
  </v-ap>
</div>

<script>
  new Vue({
    el: "#app",
    vuetify: new Vuetify(),
    data() {
      return {
        // テーブルのヘッダー情報。valueの値がレコードのフィールド名に紐付く
        headers: [
          { text: "ID", value: "id"},
          { text: "本の名前", value: "name"},
        ],
        // テーブルのボディー情報。apiで取得したBook一覧をここに格納する
        items: [],
        // 表示するページの番号
        currentPage: 1,
        // 1ページあたりの表示件数
        itemsPerPage: 10,
        // ページネーションした時の全ページ数
        totalPages: null,
      }
    },
    methods: {
      // AxiosでBook取得apiにリクエストを送る
      fetchBooks() {
        const url = `/api/books?page=${this.currentPage}?per=${this.itemsPerPage}`;
        axios
          .get(url)
          .then(res => {
            // Book一覧を取得
            this.items = res.data.books;
            // ページネーションした時の全ページ数を取得
            this.totalPages = res.data.total_pages;
        })
      }
    },
    // DOMが作成された時に fetchBooks を呼び出す
    created() {
      this.fetchBooks()
    },
  });
</script>

http://localhost:3000/books
これでページネーションは完成です
以下のようにページを切り替える度に表示が変わればOKです!

スクリーンショット 2020-05-18 23.38.17.png

スクリーンショット 2020-05-18 23.38.27.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む