20210110のRailsに関する記事は15件です。

vue.js + rails + dockerで環境構築する

ディレクトリ構成

root
 ├ Dockerfile
 ├ docker-compose.yml
 ├ Gemfile
 └ Gemfile.lock

ファイル内容

※Dockerfile

FROM ruby:2.6.5

RUN apt-get update -qq && \
    apt-get install -y build-essential \
                      libpq-dev
#yarnのインストール
RUN apt-get update && apt-get install -y curl apt-transport-https wget && \
    curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
    echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
    apt-get update && apt-get install -y yarn

#Nodejsをバージョン指定してインストール
RUN apt-get install -y nodejs

RUN mkdir /app
ENV APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
ENV APP_ROOT /app
WORKDIR $APP_ROOT

ADD ./Gemfile $APP_ROOT/Gemfile
ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock

# RUN gem install bundler -v 1.3.0 (途中から追加する場合はbundlerの指定も必要)
RUN bundle install
ADD . $APP_ROOT
※docker-compose.yml

version: '3'
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root
    ports:
      - "4306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
  web:
    build: .
    volumes:
      - .:/app
      - gem_data:/usr/local/bundle
    ports:
      - "3000:3000"
    depends_on:
      - db
    tty: true
    stdin_open: true
volumes:
  mysql_data:
  gem_data:
※Gemfile

source 'https://rubygems.org'
gem 'rails', '6.0.3.4'

※Gemfile.lockはファイルだけ作成しておけば良い。
※Rails 6.0から、Rubyのバージョンは2.5以上が必須
※バージョンが古いと何故か後でvue.jsをインストールする時にwebpackerが読み込まれない事があったのでバージョンを最新にしました。(あくまで自分の環境ですが)

ここまで出来たら一旦docker-compose upでイメージとコンテナを起動させます。

$ docker-compose up

↓コンテナが起動出来ているか確認

$ docker ps

起動したwebコンテナに入る

$ docker exec -it webのコンテナID /bin/bash

vue.jsをインストール

$ rails new アプリ名 --webpack=vue

※一旦サーバー起動してrailsの初期画面が表示されているか確認しときます。vue.jsの表示設定はまだしてません。

$ rails s -b 0.0.0.0

データベースをsqliteからmysqlへ変更する

※app/config/database.yml

# SQLite. Versions 3.8.0 and up are supported.
#   gem install sqlite3
#
#   Ensure the SQLite 3 gem is defined in your Gemfile
#   gem 'sqlite3'
#
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password # docker-compose.ymlのMYSQL_ROOT_PASSWORD
  host: db # docker-compose.ymlのservice名

development:
  <<: *default
  database: rails_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: rails_test

production:
  <<: *default
  database: rails_production

↓gem 'sqlite3' → gem 'mysql2' に変更する

※Gemfile

source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.6.5'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 6.0.3', '>= 6.0.3.4'
# Use sqlite3 as the database for Active Record
gem 'mysql2'
# Use Puma as the app server
gem 'puma', '~> 4.1'
# Use SCSS for stylesheets
gem 'sass-rails', '>= 6'
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem 'webpacker', '~> 4.0'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use Active Model has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Active Storage variant
# gem 'image_processing', '~> 1.2'

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false

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]
end

group :development do
  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
  gem 'web-console', '>= 3.3.0'
  gem 'listen', '~> 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

group :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'
  # Easy installation and use of web drivers to run system tests with browsers
  gem 'webdrivers'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

mysqlをインストール

$ bundle install

$ apt-get install mariadb-client

↓database.ymlの設定した内容でmysqlにアクセスできるか確認しときます。

$ mysql -u root -ppassword -h db

※上の-ppasswordは -p passwordと記載するとエラーになるので注意。無事アクセスできると下のようになります。

MySQL [(none)]>

*アクセス成功した場合

↓一度ログアウトしてdatabaseを作ります

$ rake db:create

↓作れたら再度mysqlにアクセスしてDBが出来ているか確認

MySQL [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| rails_development  |
| rails_test         |
| sys                |
+--------------------+
6 rows in set (0.011 sec)

vue.jsを表示する

まずはwebコンテナに入った状態でvue.jsを表示するためのcontrollerとviewを作っていきます。

$ rails g controller home index
※ app/views/home/index.html.erb

<%= javascript_pack_tag 'hello_vue' %> 

スクリーンショット 2021-01-10 23.01.44.png

とりあえずここまでいけたら一通り完了です。
※表示されなかった場合などは一旦rails sを再起動したりすると直るかもしれません。

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

Rails6 Ajaxを使えるようにするために最初にしておくこと

ポートフォリを作成中にAjaxで路頭に迷ったので、未来の自分のためにこの記事を残しておこうと思います。

jQuery関係

jQueryのライブラリをインストール

$ yarn add jquery@3.4.1

Webpackの環境編集

config/webpack/environment.js
const { environment } = require('@rails/webpacker')

#下記を追加
const webpack = require('webpack')
environment.plugins.prepend('Provide',
  new webpack.ProvidePlugin({
    $: 'jquery/src/jquery',
    jQuery: 'jquery/src/jquery'
  })
)
#ここまで
module.exports = environment

application.jsに追加

app/javascript/packs/application.js
require("jquery")

ブラウザ関係

ブラウザ側でjavascriptが無効になっていた場合でもAjaxが起動するために

config/application.rb
module "アプリ名"
  class Application < Rails::Application
    .
    .
    .
    # これを追加!
    config.action_view.embed_authenticity_token_in_remote_forms = true
  end

あとは書きまくるだけ!!

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

【Rails】よっ!Active Strage!有能なGem!〜2つのテーブルから画像を保存できる〜

ずっと謎だったこと

Active Strageで画像を保存する時に二つのテーブルから保存できるのか?ということがずっと疑問でした。
現在オリジナルアプリを開発中なのですがレビューサイトのため、
①ユーザーのプロフ写真
②レビュー対象の写真
この二つを保存したかったのですが複数のテーブルから保存する方法がいまいちよくわからなかったため、ユーザーのほうはtext型にて保存、Active Strageを使用するのはレビュー対象のみとしていました。
本日、マイグレーションをrollbackしなければいけないミスがあり、ついでだからやってみようと思い立ち、以下の作業をしました。

・ユーザーテーブルに保存していた画像保存予定だったカラムを消去
・ユーザー登録時&レビュー対象登録時にそれぞれ写真が保存できるか確認

まずはマイグレーションを編集します。

% rails db:rollback  

このコマンド、最初は実行するのがとてもとても怖かったです(初心者あるある
その後、状況がどうなっているかをしっかり確認するためステータスをチェック。

% rails db:migrate:status  

database: party_freak_development

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20210107124355  Devise create users                 ⬅️変更目標はここ
   up     20210108071047  Create parties 
   up     20210108094951  Create active storage tablesactive storage
   up     20210108122152  Rename iintroduction column to parties  ⬅️リネームした履歴
   up     20210108123328  Rename date id column to parties      ⬅️リネームした履歴
  down    20210108130921  Change column to allow null      ⬅️null制約変更した履歴

下の3つは昨日変更したテーブルのカラムの名前とnull制約に関するマイグレーションファイルです。
よくよく考えたらリネームしてマイグレーションファイルを増やすならrollbackしてマイグレーションファイルを直に修正したほうが余計なファイルが増えなくていいんじゃないか?
と気付き、rollbackを繰り返し行って下の3つのマイグレーションファイルをコードエディタ上から右クリック→削除しました。スッキリ。

繰り返す時はこのコマンドが便利です。

 % rails db:rollback STEP=5 
                ⬆️=の後にdownを0とし(次のupから1と数える)その回数だけrollback
                 今回の自分のケースだと5でした

よく見たら一瞬焦ったこと

== 20210108094951 CreateActiveStorageTables: reverting ========================
-- drop_table(:active_storage_attachments, {})
   -> 0.0470s
-- drop_table(:active_storage_blobs, {})
   -> 0.0363s
== 20210108094951 CreateActiveStorageTables: reverted (0.0847s) ===============

Active Strageがdropになってるううううううううううううう!
またやらかしたああああああああああああああああ!
消したものはしょうがないどうせまた戻るだろ前しか見えねえ(確認犯
db:migrateで、きっとまた会えるよね?そう信じて次に進む。

ユーザーモデルにActive Strageを使えるようにする記述を行います。

app/models/user.rb
class User < ApplicationRecord

  has_one_attached :image  ⬅️これを記述

〜以下略〜

end

①ユーザーモデル
②レビュー投稿モデル
合計2つのモデルにhas_one_attached :imageを記述しました。

そしてマイグレーションファイルから画像保存用の記述を削除して、db:migrateをする。

結果

5d39289236b741ec2dcc8f2c4ed856eb.png

record_typeというカラムにUserPartyという2つの表示がされています。

大成功!(やっぱり会えたねActive Strage

気になったこと

ユーザーは複数枚の画像投稿(もしくは動画)が可能なように考えているのですが、もう1つテーブルを作ってそこにhas_many_attachedを使って保存されるのだろうか?
複数の場合はhas_oneからhas_manyに変わるとのことですがこのカラムを見ているとできそうな雰囲気も感じますが、実際にやってみた結果はまたこちらで記事を書こうと思います。

以上、Active Strageで2つのテーブルから画像を保存する方法でした。

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

Railsでダブルポリモーフィック

Rails - Polymorphic Favorites (user can favorite different models)
https://stackoverflow.com/questions/21817019/rails-polymorphic-favorites-user-can-favorite-different-models

Double polymorphic association
https://stackoverflow.com/questions/15367098/double-polymorphic-association

Railsで中間テーブルダブルポリモーフィックやってみた。
https://qiita.com/kumewata/items/00e9f6994a9585d6542a

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

【個人開発】テストユーザーを募集するサイト作ってみた!

初めに

前、MENTAの作成者さんが動画で「webアプリは公開してからじゃないと使われるかわからない」って言っていたので
ベータ版専門のアプリ宣伝サイトをつくってみました。
そんなことサイトもまだベータ版でデザインは相当雑です。使われるようになってきたらデザインも変更しますし機能も追加します。

URL:
https://testuser-b.herokuapp.com/

テストユーザーはなぜ必要?

webアプリは公開しないと需要があるかわかりません。
「市場調査で調べられる」と思うかもしれないが口先だけ使うと言われただけで、公開したあとその人たちが本当に使うかはわからないんです。

だから、本当に需要があるかどうかは実際に公開してみるしかないのですが
何か月もかけて開発したのに誰にも使われなかった...というのは悲しすぎます。
webアプリは当たる確率の方が低いです(6%程)

だからヒットしなくてもダメージが少ないようベータ版を作ればいいのですが なかなか使ってもらうのは難しいです。webアプリは万全の状態で宣伝するものなので 今あるwebアプリの宣伝所はベータ版をなかなか受け入れてくれません。 そこでこのTest User βが生まれました。

test-user.png

使った技術
rails6
ruby2.7
heroku free(スリープしないよう設定している)

開発時間は数時間
ロゴは自作
開発費用もサーバー代も無料です。

まとめ

webアプリは公開してからじゃないと使われるかわからない
市場調査だけではわからない

何がともあれ使ってみてください。お願いします(^_-)-☆
URL:
https://testuser-b.herokuapp.com/

僕のツイッターもお願いします。
開発中に気づいたことをつぶやいてます。
https://twitter.com/Yuuki49079799

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

RailsでExtend(Concerns)を利用してControllerの処理を共通化する。

背景

記事一覧をPage#homeに出力したかった。
@ posts = Post.allのデータは、Postモデル、コントローラにある。Pageモデル、コントローラには無い。
PostのデータをPageに持ってくるためにはどうすればいいかググっていた。

解決

この情報をPageに持って行きたい。

posts_controller.rb
class PostsController < ApplicationController

    def index
      posts = Post.all
    end
end

Pageに取り込む。

Page.rb
class Page < ApplicationRecord

   + extend Post::Models

end

PageでPostの情報を定義してみる。

pages_controller.rb
def home
  @posts = Post.all
end

出力された。

home.html.erb
<%= @posts.each do |p| %>
 <%= p.thumbnail %>
 <%= p.title %> 
 <%= p.tag %> 
 <%= p.content %> 
<% end %> 

もっとうまく説明している記事あります。↓
https://github.com/mc-chinju/qiita_clone/commit/262a6178d5a2eb77c6f507cf9386cb61825bfbaf
https://medium.com/@yavuz255/rake-aborted-2da1233a4561
https://railsguides.jp/active_model_basics.html

他のやり方↓
controller/concernにファイルを作って行う方法
https://programming-beginner-zeroichi.jp/articles/142

ググる時の関連ワード

extend ActiveSupport::Concern
rails concern

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

yarnがインストールされていないとのこと

rails new でアプリを作ろうとすると

RubyとRailsがインストールでき、アプリを作ってみようかとすると
yarnがインストールされていませんよと言われたので、yarnをインストールして解消。

環境
Mac
Ruby2.6.0
Rails6.1.0

rails new実行

今回はデスクトップに作るので、

$ cd Desktop
$ Rails new アプリ名

実行すると、
しばらく時間がかかったのちに、

Yarn not installed. 

アプリが作られず、エラーになったようで、上記のようにyarnがインストールされていない
と言われる。

yarn インストール

参考→Rails6 開発時につまづきそうな webpacker, yarn 関係のエラーと解決方法

$ brew install yarn
$ yarn -v
$ yarn install

これで無事にインストールされた模様。

再度アプリを作りサーバーで実行

再度、rails new を実行すると今度はうまくいった。
そして、rails server を実行すると、懐かしの画面。1年半ぶり?
image.png

ん、Railsのバージョンが変わってる?
yarnのインストールによってRailsのバージョンが6.1.0から6.1.1になったのでしょうか。

$ rails -v
Rails 6.1.1

やはり変わっている。問題ないでしょう。知らんけど。
さて、Rubyの勉強を少し挟んで、Railsアプリの中身を作ることにします。

参考書籍

Ruby on Rails 6 超入門

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

Rubyで黒澤ルビィを作る(Ruby + LINE Messaging API)

黒澤ルビィ、可愛いですよね。

LINE上で喋るルビィちゃんを作りました。Rubyで。

Rubyボット01.png

以下の仕組みで動いています。

  • LINE Messaging APIを用いて、「黒澤ルビィ」としてLINEメッセージの受信、送信を行う。
  • Ruby(Ruby on rails)LINE Messaging APIとやり取りするAPIサーバを作る。
    • メッセージを形態素解析し、その結果をもとにマルコフ連鎖で新しく文章を生成する

LINEアカウントを作る

前準備としてルビィちゃんとして動くアカウント(厳密に言うと、Messaging APIのチャネル)が必要です。それをつくります。

LINE Developersから登録します。
Rubyボット02.png
Create a new channelから新しく作ります。なお、作るときにどういうTypeか聞かれると思います。今回作りたいものはMessaging APIで作ることができるので、該当するものを選択してください。

無事作成できると、アクセストークンを取得できます。
また、アカウントのアイコンや自己紹介欄などを設定できます。1

APIサーバを作る

画面を持つ予定はないので、APIモードでRailsプロジェクトを作ります。
line-bot-apigem2を入れます。先程作成したアカウントのアクセストークンを使ってLINEとやりとりできるようになります。

Controllerを作る

先程作成したアカウントに対してWebhook URLを設定すると、そのアカウントに対して送信したメッセージをトリガーにしてLINEがリクエストを送ってくれます。
具体的なリクエストの中身についてはMessaging APIリファレンスを参照してください。以下の例はテキストメッセージに反応して、「メッセージ」と返すだけの仕組みです。

def client
  @client ||= Line::Bot::Client.new { |config|
    config.channel_secret = ENV["LINE_CHANNEL_SECRET"]
    config.channel_token = ENV["LINE_CHANNEL_TOKEN"]
  }
end

def callback
  body = request.body.read

  signature = request.env['HTTP_X_LINE_SIGNATURE']
  head :bad_request unless client.validate_signature(body, signature)

  events = client.parse_events_from(body)

  events.each do |event|
    next unless event.is_a?(Line::Bot::Event::Message)
    next unless event.type == Line::Bot::Event::MessageType::Text

    client.reply_message(event['replyToken'], message)
  end
  head :ok
end

def message
  # cf. https://developers.line.biz/ja/reference/messaging-api/#text-message
  {
    "type": 'text',
    "text": 'メッセージ'
  }
end

このmessageに相当する部分を、マルコフ連鎖で生成した文章にさせます。

マルコフ連鎖で文章を作る

マルコフ連鎖による文章作成の大枠については解説しているものがたくさんあるので、詳しくは「マルコフ連鎖 文章作成」とかで検索してください。

ざっくり説明すると、あらかじめ形態素解析(言語で意味をもつ最小単位に分解すること)によって言葉のつながりを学習させ、ある単語に対してそれに続く言葉を過去の学習から確率的に選択していくことでそれっぽい文章ができるやつです。

形態素解析する

形態素解析は、Yahoo!が提供している日本語形態素解析を使っています。MeCab(Natto)を使って、オタク用語に特化した辞書を入れるのもアリかなと思います。

LINEメッセージが送られたらそのメッセージをそのまま形態素解析します。結果をパースし、言葉と品詞がセットとなったハッシュの配列にします。3
単語として保存するWordモデルと、つながりを保存するMarkovDicモデルを作り、それぞれ保存します。

Wordモデル

カラム名 説明
pos String 品詞(例:名詞)
surface String 単語(例:庭)

MarkovDicモデル

カラム名 説明
prefix_1 String 単語のつながりをA-B-Cと表現したときのA (例:私)
prefix_2 String 単語のつながりをA-B-Cと表現したときのB (例:は)
suffix String 単語のつながりをA-B-Cと表現したときのC (例:犬)
  # 形態素解析を行い、WordレコードとMarkovDicレコードを作成する。名詞の配列を返す。
  def record(text, user)
    text = remove_url(text)  # URLを取り除いたりしている
    morphological_words = exec(text)

    poses = save_words(morphological_words, user)
    save_markov_dics(morphological_words)

    poses
  end

  # 単語を保存する。
  def save_words(morphological_words)
    words = []

    morphological_words.each do |word|
      w = Word.new(word)
      words << w.attributes.compact!.merge({ created_at: now, updated_at: now })
    end
    Word.insert_all(words) if words.present?
  end

  def save_markov_dics(morphological_words)
    markov_dics = []
    morphological_words.each_cons(3) do |p1, p2, suf|
      next if p1['surface'] == "\n"
      next if p2['surface'] == "\n"

      md = MarkovDic.new(prefix_1: p1['surface'], prefix_2: p2['surface'], suffix: suf['surface'])
      md.suffix = 'END_OF_SENTENCE' if md.suffix == "\n" # 文章の終わりを意味するフラグ
      markov_dics << md.attributes.compact!.merge({ created_at: now, updated_at: now })
    end
    eos = morphological_words.last(2)
    if eos.size == 2
      p1 = eos[0]
      p2 = eos[1]
      md = MarkovDic.new(prefix_1: p1['surface'], prefix_2: p2['surface'], suffix: 'END_OF_SENTENCE')
      markov_dics << md.attributes.compact!.merge({ created_at: now, updated_at: now })
    end
    MarkovDic.insert_all(markov_dics) if markov_dics.present?
  end

文章を作る

LINEに送られたメッセージを保存することができたので、今度は文章を作ります。
始まりの言葉は受け取ったLINEのメッセージからランダムに名詞で始まるMarkovDicを選んでいます。
ざっくり以下のような感じです。

  def create_sentence(text, markov_dic)
    return text if markov_dic.suffix == 'END_OF_SENTENCE'

    next_markov_dic = MarkovDic.where(prefix_1: markov_dic.prefix_2, prefix_2: markov_dic.suffix).sample(1).first
    return text unless next_markov_dic

    text << markov_dic.suffix
    create_sentence(text, next_markov_dic)
  end

  def prefixes
    prefix_1 + prefix_2
  end

Webhook URLを設定する

作成したAPIサーバをデプロイします。(今回はHerokuにデプロイしました。)
最初に作成したアカウントの設定画面でWebhook URLを設定し、メッセージがきたタイミングでLINEがリクエストを送ってくれるようにします。
Rubyボット03.png

LINEグループに追加します。いい感じにレスポンスしてくれます。

Rubyボット05.png

その他

  • グループラインにルビィちゃんを混ぜたところ、「かわいい」「キメラが誕生した」「新しいおもちゃ」と非常に好評でした。
  • クソデカ羅生門を覚えさせた人がいてめちゃくちゃになりました。
  • グループラインにBotアカウントは一つまでしか入れられないようです。黒澤姉妹で話すことはできませんでした。
  • 常に返信がくるとグループラインがやかましくなるので、ランダムで投稿したりしなかったりさせています。(ちなみに直接ルビィちゃんに話しかけると100%返信が来ます。)
  • push_messageを使うとルビィちゃんから話しかけることもできます。
  • 本当はアカウントを紹介したいのですが、記録した言葉の中に含まれる個人情報を考慮すると中々難しいものがあります。
  • 今の所ルビィちゃん要素が0なので、ルビィちゃんっぽくさせたいです。

まとめ

LINE Messaging APIRubyを用いて黒澤ルビィを作りました。

LINE Botは意外と簡単に作ることができるので、ぜひ作ってみてください。


  1. 開発用のアカウントも合わせて作ったりしています。ちなみに画像は友達が描いたやつです。 

  2. line/line-bot-sdk-ruby: LINE Messaging API SDK for Ruby 

  3. 以下を参考にしました。Ruby on Rails で Yahoo のテキスト解析 APIを使う-スケ郎のお話 

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

Railsチュートリアルで作ったサンプルアプリの「ファイルを選択」ボタンをかっこよくしてみた

環境

  • Windows10 Home ver.2004
  • ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x64-mingw32]
  • Rails 6.1.0

はじめに

 Railsチュートリアルの13.4.1章では、サンプルアプリに画像付きマイクロポスト投稿機能を追加するのだが、デフォルトだと画像を選ぶ際にクリックする「ファイルを選択」ボタンがダサい他のボタンとの統一感のなさのせいで目立ってしまう。
スクリーンショット 2021-01-09 155648.png
 この状況をなんとかしたいと思い、色々試してみた。

結果

 デフォルトのボタンを非表示にした上で別のボタン「Choose an image」を用意し、そのボタン経由でデフォルトのボタンを押させることによって、機能を損なわずに見た目に統一感を出すことができた。
スクリーンショット 2021-01-09 174852.png
 マウスオーバー処理も含め、下の「Post」ボタンのデザインをできる限り忠実に再現した。
スクリーンショット 2021-01-09 174923.png
 もちろん、デフォルトの動作と同様に、画像を選択した場合はそのファイル名が表示される。
スクリーンショット 2021-01-09 174947.png

 変更・追加したコードは次の通り。なお、今回は勉強のためにBootstrap用のクラスはあえて使わずに実装してみた。

app\views\shared\_micropost_form.html.erb
・
・
・
  <button type="button" class="general_button" onclick="document.getElementById('micropost_image').click()">
    Choose an image
  </button>
  <div id="image_name">No image is chosen...</div>
・
・
・
<script type="text/javascript">
  $("#micropost_image").bind("change", function() {
    let chosen_image = this.files[0];
    if (chosen_image.size/1024/1024 > 5) {
      alert("Maximum file size is 5MB. Please choose a smaller file.");
      $("#micropost_image").val("");
      document.getElementById('image_name').innerHTML = "No image is chosen...";
    }
    else {
      document.getElementById('image_name').innerHTML = "Chosen image: " + chosen_image.name;
    }
  });
</script>
app\assets\stylesheets\custom.scss
.general_button{
    color: white;
    background-color: $blue-for-button; // #337ab7
    border: 0.5px solid $blue-for-button-border; // #2e6da4
    border-radius: 3.5px;
    padding-top: 6.5px; padding-bottom: 6.5px;
    padding-right: 10px; padding-left: 10px;
    &:hover {
        background-color: $dark-blue-for-button; // #285f90
        border-color: $dark-blue-for-button-border; // #204d74
    }
}

#image_name {
    margin-top: 1px;
    margin-left: 1px;
    margin-bottom: 9px;
}

#micropost_image{
    visibility: hidden;
}

結果までの流れ

1.

 適当なキーワードで検索したらいくつか参考にできそうな記事12がヒットしたが、それらをそのまま自分の状況に適用するのは難しそうなので、具体的なコードの書き方は自分で考えてみることにした。

2.

 あるオブジェクトがクリックされたことをトリガーとして別のオブジェクトのイベントを作動させるという処理を実現するコードの書き方を調べるため、「javascript html ボタン 押させる」などのキーワードで検索したら、「JavaScriptでボタンを押すアクションを起こす | forWEB屋」がヒットした。
 このサイトを基に、デフォルトの「ファイルを選択」ボタンのクリックイベントを作動させるための別のボタン「Choose an image」を作った(_micropost_form.html.erb内参照)。

3.

 続いて、ユーザーが選択した画像ファイル名をどうやって表示しようかと考えた結果、Railsチュートリアル内のリスト13.67に書かれていた、選択された画像のファイルサイズが5MBより大きかった場合にアラートを出すJavaScriptコードを改造することにした(_micropost_form.html.erb内参照)。
 途中でf.file_fieldに独自のidを追加したらアラートを出すJavaScriptが動作しなくなるトラブルが起こったが、JavaScriptコードを読んで既にidとしてmicropost_imageが設定されていることに気付き、独自のidを削除したら再び動作するようになった。

4.

 これまでで動作に関する部分の実装は概ね終わっていたので、「CSSリファレンス - とほほのWWW入門」を参考に、時には画像編集ソフトのスポイト機能を使ってスクリーンショットから色の情報を取ったりしつつcustom.scssを編集した。

以上


  1. [Rails][file_field]画像のアップロードのボタンデザインを変更する - Qiita(https://qiita.com/Yukina_28/items/4a8332354f6cb7c7a6f6

  2. railsのfile_fieldで画像クリックによりアップロード - Qiita(https://qiita.com/zukakosan/items/41ed95fea2323cf458a9

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

【Ruby On Rails / HTML】ウェブページ表示時、カーソル(キャレット)を適当な場所へ合わせ表示する方法

備忘録です。

カーソルとキャレットとは

スクリーンショット 2021-01-10 15.31.03.png

キャレット≒カーソル
マウスカーソルなどと区別したい場合にキャレットという言葉が用いられます。

本題

カーソルを適当な場所へフォーカスするには、autofocusを使います。
Rubyでは、次のような書き方をします

autofocus: true

それでは、Deviseの新規登録画面に"Nickname"という項目を設けた場合で見ていきます。
まずは"Nickname"にautofocusを使わない場合はどのように表示されるでしょうか。

①"Nickname"にautofocusなし

new.html.erb
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

  <div class="field">
    <%= f.label :nickname, "Nickname" %><br />
    <%= f.text_field :nickname %>
  </div>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

(後略)

スクリーンショット 2021-01-10 15.39.34.png

上記の場合、新規登録のウェブページを開いてみると、Nicknameではなく、Emailにカーソルがフォーカスされています。

次に、<%= f.text_field :nickname %>にautofocusを付与して、Nicknameにカーソルの焦点を当てた状態で見ていきます。

②"Nickname"にautofocusあり

new.html.erb
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

  <div class="field">
    <%= f.label :nickname, "Nickname" %><br />
    <%= f.text_field :nickname, autofocus: true %>
  </div>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

(後略)

スクリーンショット 2021-01-10 15.47.50.png

このようにautofocusを"Nickname"に付与してあげることで、自動的に一番最初にカーソルが当てられる場所がNicknameとなりました。

参考記事

https://www.sophia-it.com/content/%E3%82%AD%E3%83%A3%E3%83%AC%E3%83%83%E3%83%88
http://www.htmq.com/html5/input_autofocus.shtml

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

Rails環境構築 with Docker

参考サイト

Docker超入門 :きよとのプログラミング大学
丁寧すぎるDocker-composeによるrails5 + MySQL on Dockerの環境構築(Docker for Mac)

環境

Mac OS
docker-compose 1.27.4
Mysql 8.0
ruby 2.7
rails 6.1.0

ファイル作成・記述

terminal.
 ~ % cd Desktop
Desktop % mkdir rails_on_docker
Desktop % cd rails_on_docker
rails_on_docker % tree  
├── Dockerfile
├── docker-compose.yml
└── src
    └── Gemfile
1 directory, 3 files
rails_on_docker % code .

docker-compose-yml

docker-compose-yml
version: '3'
# 管理するコンテナの記述
services:
  # データベース
  db:
    image: mysql:8.0
    # Mysqlの認証設定
    command: --default-authentication-plugin=mysql_native_password
    volumes:
    # ローカルのデータ:dockerのデータ 同期するための記述
      - ./src/db/mysql_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: password
  # rails
  web:
    # ディレクトリ直下のDockerfileを参照
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
    # ローカルのデータ:dockerのデータ 同期するための記述
      - ./src:/app
    ports:
    # ローカルのポート:dockerのポート
      - "3000:3000"
    depends_on:
    # Myaqlに接続するための記述
      - db

Dockerfile

Dockerfile
# ベースイメージの指定
FROM ruby:2.7

# 必要なライブラリのインストール
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
  && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
  && apt-get update -qq \
  && apt-get install -y nodejs yarn 
#   作業ディレクトリの指定
WORKDIR /app
# ソース以下のファイルをコピー
COPY ./src /app
# rubyに必要なgemのインストール
RUN bundle config --local set path 'vendor/bundle' \
  && bundle install

Gemfile

Gemfile
# railsのgemをインストールするための雛形
source 'https://rubygems.org'

gem 'rails','~> 6.1.0'

railsのインストール

使用するコマンド

terminal.
% docker-compose run <サービス> <コマンド>

railsのインストール

terminal.
rails_on_docker % docker-compose run web rails new . --force --database=mysql  // dbをmysqlに指定しrailsのインストール --force(強制的に実行オプション)
.
.
.
Webpacker successfully installed ? ?

イメージのビルド

terminal.
rails_on_docker % docker-compose build 

新しく作成されたdatabase.ymlに追記

database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password  //記述
  host: db  //記述

データベースの作成

terminal.
rails_on_docker % docker-compose run web rails db:create  

起動

terminal.
rails_on_docker % docker-compose up

スクリーンショット 2021-01-10 15.41.57.png

まとめ

途中エラーが幾つか発生しましたが、DockerはもちろんRails,Mysqlを学ぶいい機会となりました。
次は本番環境(heroku)へのデプロイもまとめます。

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

バックエンドの設計を任されたので、エンドポイント設計をしっかり学んでみる

はじめに

こんにちは、大学を休学してスタートアップでWebエンジニアとして働いてる、おにかんです。

現在、関わっているプロジェクトのエンドポイント設計を任されたので良い機会だと思い、1から勉強して、まとめてみました。何かご指摘等ありましたらコメントしていただけるとありがたいです。

エンドポイント設計を始めよう!

*そもそもエンドポイントとは?

APIにアクセスするためのURIという認識で問題ないかと思います。

基本的な考え方

URI設計は「シンプルでわかりやすい」ものであるべきです。なぜ「シンプルでわかりやすい」URIが良いのでしょうか?主な理由に「開発者のミスを減らすことができる」ということが挙げられます。例えば、下記の二つのURI、どちらがわかりやすいでしょうか?

api/v1/p/c
api/v1/posts/comments

前者の方は頭文字しかURIに反映されていなく、わかりにくいですよね。一方、後者はある投稿のコメントのリソースなのだな、となんとなく想像がつくかと思います。URIをわかりやすくすることで、開発者のミスを減らすことができ、結果的にサービスの品質が向上します。

「シンプルでわかりやすい」URI設計にするために、以降から具体的なtipsをまとめておきました。

【1】第三者が読んで理解できる

api/v1/p/c

上記のようなエンドポイントでは何を示しているのか予想しにくいですよね。上記のような場合では少なくとも下記のように明確にリソースを示してあげた方が親切です。

api/v1/posts/comments

【2】基本的には全て小文字を使う

どちらかに統一したほうがわかりやすいのでミスが減ります。

【3】他のエンドポイントと整合性をとる

例えば下記のようなエンドポイントは整合性が取れていません。(少し奇妙ですが)

api/v1/posts/:id
api/v1/friends/?id=1

上記は極端な例ですが、下記のようにできるかぎり形式を統一させたほうがわかりやすいかと思います。

api/v1/posts/:id
api/v1/friends/:id

基本的には、この三つが大切です。もっと細かい点で言うと

  • 変更しやすいURL
  • サーバーのアーキテクチャがわかりずらいURL

といったところでしょうか。

HTTPメソッドとエンドポイントの関係

URIとHTTPメソッドはそれぞれ、下記のような役割を持ちます。

URI:リソース(情報)
HTTPメソッド:動作

つまり、URI(情報)をHTTPメソッドで操作することができます。例えば、HTTPメソッドのGETは「取得」を表すので

GET api/v1/posts/100

とすると、PostのIDが100のデータ(リソース)を取得(動作)することができます

大切なことはURIと、HTTPメソッドは分離しているということです。URIで動作(例えば、取得や削除など)を指定することは推奨されませんし、HTTPメソッドがリソースの領域を侵すこともおすすめできません。

以下から主要なHTTPメソッドについて軽く解説していきます。

GET

GETメソッドは「取得」を示します。URIで指定されたリソースを取得してクライアントに返します。下記は例です。

GET api/v1/posts/100

大切なことはGETメソッドで「情報(リソース)」の内容が変化してはいけない、ということです。リソースを作成したい場合は基本的にはPost、更新はPUT、またはPATCH、削除にはDELETEメソッドを使うべきです。

POST

POSTメソッドは「作成」を示します。もっと言うと、「指定したURIに属するリソースを送信する」と言うことです。例を見てみましょう

POST api/v1/posts

GETの時にあった、id指定がなくなっているのが確認できるかと思います。つまり、Postsに属する形でリソースが作られるということです。基本的に、POSTは「新規作成」に使われるべきであり、既存のリソースに対して何かしらの操作をしたい場合は、PUTDELETEを使うと良いと思います。

PUT

PUTメソッドは情報を「更新」される場合に使われます。PUTメソッドはPOSTと違って、更新したいURIを指定します。下記は例です。(実際にはクエリパラメータでデータを指定したりするのですが、今回は割愛)

PUT api/v1/posts/100

POSTメソッドと違って、直接指定しているのが確認できると思います。(POSTは従属するリソースを作成する。)
ちなみに指定したリソースが存在しない場合、リソースの作成が行われます。しかし、リソースの作成には基本的にPOSTメソッドを使うべきです。

DELETE

DELETEメソッドは指定したURIのリソースの「削除」を行います。

DELETE api/v1/posts/100

PATCH

PATCHメソッドは、PUTと同じように「更新」の役割を持ちますが、全ての情報を更新するのではなく、一部の情報を更新します。

HTTPメソッドについては以上です。以降から、URIとHTTPメソッドを組み合わせて、具体的なエンドポイントの設計をしていきたいと思います。

実際にエンドポイントを設計する

基本的なリソースとHTTPメソッドについて見ていきました。ここから実際に二つを組み合わせて見ていきましょう!

今回はmemberというデータモデルに対して、エンドポイントを考えてみます。

個別のデータを取得する

GET /members/:id

membersという集合データをidで特定するというイメージです。基本的にはこの形が一般的です。

全体を取得する

GET /members

membersの全ての情報を取得しています。また検索等は、このエンドポイント内のクエリパラメータで制御するイメージです。

データの新規作成

POST /members

今度はid指定されていません。これはPOSTメソッドであるため、membersに従属するデータを新規作成するというところからきています。

データの更新

PUT /members/:id

更新では、実際に更新するmemberを指定してあげるため、id情報を与えています。

データの削除

DELETE /members/:id

削除の場合も同様にmemberを指定してあげるため、id情報を与えています。

基本的な部分は以上です。あとは実戦でフィードバックをもらいながらやっていくのがいいのかなと思います。

ではでは〜

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

Rails+Vue+devise_token_authでCRUD書いてみた

はじめに

フロントエンドサーバーとバックエンドサーバーを分けて開発してみたいと思ったので
Vue.jsとRails(APIモード)でログイン機能のついたCRUD処理を書いてみました。

環境

Ruby: 2.6.5
Ruby on Rails 6.0.3

セットアップ

Railsプロジェクト作成

rails _6.0.3_ new devise_token_auth_api --api -d postgresql

cd devise_token_auth_api

rails db:create

Gemfile

gem "devise"
gem "devise_token_auth"
gem "rack-cors"

devise_token_auth インストール

bundle install

rails g devise:install

rails g devise_token_auth:install User auth
config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'localhost:8080'

    resource '*',
      headers: :any,
      expose: ['access-token', 'expiry', 'token-type', 'uid', 'client'],
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end
config/initializers/devise_token_auth.rb
DeviseTokenAuth.setup do |config|
  config.change_headers_on_each_request = false
  config.token_lifespan = 2.weeks
  config.token_cost = Rails.env.test? ? 4 : 10
end

Userモデル

app/models/user.rb
class User < ActiveRecord::Base
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  include DeviseTokenAuth::Concerns::User
end
rails db:migrate

ルーティング

config/routes.rb
Rails.application.routes.draw do
  namespace :v1 do
    mount_devise_token_auth_for "User", at: "auth"
  end
end

API動作確認

今回はPostmanを使用します。
Devise_token_authの使い方は下記URLを参考にします。
https://devise-token-auth.gitbook.io/devise-token-auth/usage

サインアップ

HTTPメソッド: POST
URL: http://localhost:3000/v1/auth/
Body: email, password
スクリーンショット 2021-01-10 10.59.15.png

サインイン

HTTPメソッド: POST
URL: http://localhost:3000/v1/auth/sign_in
Body: email, password
スクリーンショット 2021-01-10 11.05.43.png

サインアウト

HTTPメソッド: DELETE
URL: http://localhost:3000/v1/auth/sign_in
Body: uid, access-token, client

サインイン処理した際、Headersタブに必要なパラメータを確認することができます。
スクリーンショット 2021-01-10 11.14.54.png

Vueプロジェクト作成

vue create frontend
*Default ([Vue 2] babel, eslint)を選択

cd frontend
yarn add axios

cd ..

起動コマンド

cd frontend
yarn serve

画面作成

frontend/src/App.vue
<template>
  <div id="app">
    <h3>掲示板に投稿する</h3>
    <div v-if="client === ''">
      <div>
        <h1>SignUp</h1>
        <label for="email">email</label>
        <input id="email" type="email" v-model="email" />
        <label for="password">password</label>
        <input id="password" type="password" v-model="password" />
        <button @click="signup">新規登録</button>
      </div>
      <div>
        <h1>SignIn</h1>
        <label for="email">email</label>
        <input id="email" type="email" v-model="email" />
        <label for="password">password</label>
        <input id="password" type="password" v-model="password" />
        <button @click="signin">SignIn</button>
      </div>
    </div>
  </div>
</template>

<script>
import axios from "axios";
export default {
  data() {
    return {
      name: "",
      email: "",
      password: "",
      uid: "",
      access_token: "",
      client: "",
      title: "",
      content: "",
      tasks: [],
      comment: "",
      posts: [],
    };
  },
  methods: {
    signup() {
      axios
        .post("http://localhost:3000/v1/auth", {
          email: this.email,
          password: this.password,
        })
        .then((response) => {
          localStorage.setItem(
            "access-token",
            response.headers["access-token"]
          );
          localStorage.setItem("client", response.headers["client"]);
          localStorage.setItem("uid", response.headers["uid"]);
          this.access_token = response.headers["access-token"];
          this.client = response.headers["client"];
          this.uid = response.headers["uid"];

        });
    },
    signin() {
      console.log(this.email);
      console.log(this.password);
      axios
        .post("http://localhost:3000/v1/auth/sign_in", {
          email: this.email,
          password: this.password,
        })
        .then((response) => {
          console.log(response);
          localStorage.setItem(
            "access-token",
            response.headers["access-token"]
          );
          localStorage.setItem("client", response.headers["client"]);
          localStorage.setItem("uid", response.headers["uid"]);
          this.access_token = response.headers["access-token"];
          this.client = response.headers["client"];
          this.uid = response.headers["uid"];

        });
    },
    signout() {
      console.log(this.uid);
      console.log(this.access_token);
      console.log(this.client);
      axios
        .delete("http://localhost:3000/v1/auth/sign_out", {
          test: { test: "test" },
          headers: {
            uid: this.uid,
            "access-token": this.access_token,
            client: this.client,
          },
        })
        .then((response) => {
          console.log(response);
          this.access_token = "";
          this.client = "";
          this.uid = "";
          localStorage.removeItem("uid");
          localStorage.removeItem("access-token");
          localStorage.removeItem("client");
        });
    },
  },
};
</script>
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

SignIn画面

スクリーンショット 2021-01-10 11.24.18.png

タスク機能追加

rails g controller v1/tasks
rails g model Task

モデルの追加

db/migrate/YYYYMMDD_create_tasks.rb
class CreateTasks < ActiveRecord::Migration[6.0]
  def change
    create_table :tasks do |t|
      t.string :title
      t.text :content
      t.references :user
      t.timestamps
    end
  end
end
rails db:migrate

ルーティング

config/routes.rb
Rails.application.routes.draw do
  namespace :v1 do
    resources :tasks
    mount_devise_token_auth_for 'User', at: 'auth'
  end
end

コントローラー

app/controllers/v1/tasks_controller.rb
class V1::TasksController < ApplicationController

  before_action :set_task, only: [:show]
  before_action :authenticate_v1_user!
  def index
    tasks = Task.where(user_id: @current_v1_user.id)
    render json: tasks
  end

  def create
    user = User.find_by(email: params[:uid])
    task = Task.new(title: params[:title], content: params[:content], user_id: user.id)
    task.save
  end

  def show
    render json: @task
  end

  def update
  end

  def destroy
    task = Task.find(params[:id])
    task.destroy
    render json: true
  end

  private
  def set_task
    @task = Task.find(params[:id])
  end

end
frontend/src/App.vue
<template>
  <div id="app">
    <div v-if="client === ''">
      <div>
        <h1>SignUp</h1>
        <label for="email">email</label>
        <input id="email" type="email" v-model="email" />
        <label for="password">password</label>
        <input id="password" type="password" v-model="password" />
        <button @click="signup">SignUp</button>
      </div>
      <div>
        <h1>SignIn</h1>
        <label for="email">email</label>
        <input id="email" type="email" v-model="email" />
        <label for="password">password</label>
        <input id="password" type="password" v-model="password" />
        <button @click="signin">SignIn</button>
      </div>
    </div>
    <div v-if="client !== ''">
      <div>
        <h1>Task</h1>
        <button @click="signout">SignOut</button>
        <div v-for="task in tasks" :key="task.id">
          Task:{{ task.id }}, {{ task.title }}, {{ task.content }}
          <button @click="find_task(task.id)">task_find</button>
          <button @click="delete_task(task.id)">Delete</button>
        </div>
      </div>
      <div>
        <h3>Task</h3>
        <label for="task">task</label>
        <input id="task" type="text" v-model="title" />
        <label for="content">content</label>
        <input id="content" type="text" v-model="content" />
        <button @click="create_task">Create_Task</button>
      </div>
    </div>
  </div>
</template>

<script>
import axios from "axios";
export default {
  data() {
    return {
      name: "",
      email: "",
      password: "",
      uid: "",
      access_token: "",
      client: "",
      title: "",
      content: "",
      tasks: [],
      comment: "",
      posts: [],
    };
  },
  methods: {
    signup() {
      axios
        .post("http://localhost:3000/v1/auth", {
          email: this.email,
          password: this.password,
        })
        .then((response) => {
          localStorage.setItem(
            "access-token",
            response.headers["access-token"]
          );
          localStorage.setItem("client", response.headers["client"]);
          localStorage.setItem("uid", response.headers["uid"]);
          this.access_token = response.headers["access-token"];
          this.client = response.headers["client"];
          this.uid = response.headers["uid"];

          this.all_tasks();
        });
    },
    signin() {
      console.log(this.email);
      console.log(this.password);
      axios
        .post("http://localhost:3000/v1/auth/sign_in", {
          email: this.email,
          password: this.password,
        })
        .then((response) => {
          console.log(response);
          localStorage.setItem(
            "access-token",
            response.headers["access-token"]
          );
          localStorage.setItem("client", response.headers["client"]);
          localStorage.setItem("uid", response.headers["uid"]);
          this.access_token = response.headers["access-token"];
          this.client = response.headers["client"];
          this.uid = response.headers["uid"];

          this.all_tasks();
        });
    },
    signout() {
      console.log(this.uid);
      console.log(this.access_token);
      console.log(this.client);
      axios
        .delete("http://localhost:3000/v1/auth/sign_out", {
          test: { test: "test" },
          headers: {
            uid: this.uid,
            "access-token": this.access_token,
            client: this.client,
          },
        })
        .then((response) => {
          console.log(response);
          this.access_token = "";
          this.client = "";
          this.uid = "";
          localStorage.removeItem("uid");
          localStorage.removeItem("access-token");
          localStorage.removeItem("client");
        });
      this.tasks = [];
    },
    all_tasks() {
      axios
        .get("http://localhost:3000/v1/tasks", {
          headers: {
            uid: this.uid,
            "access-token": this.access_token,
            client: this.client,
          },
        })
        .then((response) => {
          console.log(response.data);
          this.tasks = response.data;
        });
    },
    find_task(task_id) {
      axios
        .get(`http://localhost:3000/v1/tasks/${task_id}`, {
          headers: {
            uid: this.uid,
            "access-token": this.access_token,
            client: this.client,
          },
        })
        .then((response) => {
          console.log(response);
          this.task = response.data;
        });
    },
    create_task() {
      console.log(this.uid);
      console.log(this.access_token);
      console.log(this.client);
      axios
        .post("http://localhost:3000/v1/tasks", {
          uid: this.uid,
          "access-token": this.access_token,
          client: this.client,
          title: this.title,
          content: this.content,
        })
        .then((response) => {
          console.log(response);
          this.all_tasks();
        });
    },
    delete_task(task_id) {
      axios
        .delete(`http://localhost:3000/v1/tasks/${task_id}`, {
          headers: {
            uid: this.uid,
            "access-token": this.access_token,
            client: this.client,
          },
        })
        .then((response) => {
          console.log(response);
          this.all_tasks();
        });
    },
  },
};
</script>
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

Task画面

スクリーンショット 2021-01-10 11.51.00.png
GitHub
https://github.com/yodev21/devise_token_auth_app

終わりに

フロントエンドとバックエンドを分けた際のログイン機能がついたCRUDを実装してみて
ハマるポイントが多数あり学ぶきっかけになりました。

まだまだ改善点や試したいことがあるため、今後は下記を中心に学んでいきたいと思います。
・ 複数テーブルをまたぐデータ取得機能
・ Vue Routerの導入
・ Vuexの導入

参考記事

[Rails] devise token auth を使う

devise-token-auth

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

【rails6】webpackを使って手動でjsをコンパイルしながら行う開発が非常にスムーズで感動した件

webpackを使って手動でコンパイルする際の開発の進め方

componentを編集したとき

% cd frontend #webpack.config.jsが置かれているディレクトリ
% webpack #webpack.config.jsを使ってアプリで読み込むjsをbuild

とコマンドを入力することでcomponentの変更が反映される。このとき別タブでrailsのサーバーを起動しっぱなしでも問題ない。よって開発するときは

タブ1 タブ2
railsのサーバーが動いているタブ webpack.config.jsが入っているディレクトリ
componentの編集→webpackコマンドでbuildを行う

といった形でタブを分けると開発がスムーズである。

componentも事前にwebpackでコンパイルしているのでrailsアプリでは出力されたjs一つを読み込むだけで済む。
ビューをrailsのviews以下のビューファイルで読み込むのと処理速度がかなり違う印象。

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

RSpecの導入&関係の深いgemについて

Ruby on Railsのテスト用のgemであるRSpecの導入と関係するgemについて整理します。

RSpecとは

Ruby on Railsのテストコードを書くために用いられるgemです。
実際のgem名は「rspec-rails」です。

ユーザーの新規登録やログイン、メッセージの投稿や編集などの機能をテストするために使用します。

Rubyには標準でmini_testという別のテスト用Gemが導入されているのですが、開発現場ではRSpecが主流だそうです。

RSpecのインストール

Gemfileに以下のように記述し、ターミナルでbundle installを入力します。

Gemfile
#group :development, :testというグループの中に記述する。
group :development, :test do
  gem 'rspec-rails', '~> 4.0.0'
end
ターミナル
bundle install

RSpecの設定をする

ターミナルで以下のとおり入力して、ディレクトリとファイルを生成します。

ターミナル
rails g rspec:install
#上記実行後、下記のディレクトリとファイルが生成される
    create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

.rspecファイルに以下の記述をします。テストコードの実行結果をターミナル上に可視化するために記述します。

.rspec
--require spec_helper
--format documentation #この一文を追加

RSpecについてはここまです。

RSpecと関係の深いgem

RSpecとよく一緒に使用するgemとしてFactoryBotとFakerがあります。

FactoryBotとは

インスタンスをまとめることができるgemで、他のファイルであらかじめ各クラスのインスタンスに定める値を設定しておき、各テストコードで使用します。

RSpecと同様にGemfileのgroup :development, :testというグループの中に記述してからインストールします。

Gemfile
group :development, :test do
  gem 'factory_bot_rails'
end
ターミナル
bundle install

簡単な紹介になりますが、FactoryBotは以下のように記述して使います。※アプリにUsersテーブルがあると想定

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name {"sample"}
    email {"sample@mail"}
    password {"sample777"}
  end
end

このように記述することで、テストで使用するユーザーの情報を事前に用意しておくことができます。

Fakerとは

ランダムな値を生成するgemで,メールアドレス、人名、パスワードなどのランダムな値を生成してくれます。
RSpecと同様にGemfileのgroup :development, :testというグループの中に記述してからインストールします。

Gemfile
group :development, :test do
  gem 'faker '
end
ターミナル
bundle install

Fakerはこんな感じでランダムな値を生成します。

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name { Faker::Name.initials(number: 5) }
    email { Faker::Internet.free_email }
    password { Faker::Internet.password(min_length: 6) }
  end
end

FactoryBotとFakerでできること

テストコードを実行するたびにランダムな値でインスタンスを生成することができます。
テストをするアプリにユーザー管理機能がある場合、emailなどのカラムをテーブルに用意されていると思いますが、複数のテストを行う際に、「すでにemailの重複したインスタンスが存在する」といったような意図しない形でテストエラーが出てしまうことを防ぐのに役立ちます。

今回はここまでで、次の記事でRSpecを使った単体テストについて取り上げたいと思います。

参考資料

RSpecの公式GitHub
FactoryBot(factory_bot_rails)の公式GitHub
Fakerの公式GitHub

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