- 投稿日:2021-01-10T23:06:59+09:00
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/bashvue.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' %>とりあえずここまでいけたら一通り完了です。
※表示されなかった場合などは一旦rails sを再起動したりすると直るかもしれません。
- 投稿日:2021-01-10T22:46:30+09:00
Rails6 Ajaxを使えるようにするために最初にしておくこと
ポートフォリを作成中にAjaxで路頭に迷ったので、未来の自分のためにこの記事を残しておこうと思います。
jQuery関係
jQueryのライブラリをインストール
$ yarn add jquery@3.4.1Webpackの環境編集
config/webpack/environment.jsconst { environment } = require('@rails/webpacker') #下記を追加 const webpack = require('webpack') environment.plugins.prepend('Provide', new webpack.ProvidePlugin({ $: 'jquery/src/jquery', jQuery: 'jquery/src/jquery' }) ) #ここまで module.exports = environmentapplication.jsに追加
app/javascript/packs/application.jsrequire("jquery")ブラウザ関係
ブラウザ側でjavascriptが無効になっていた場合でもAjaxが起動するために
config/application.rbmodule "アプリ名" class Application < Rails::Application . . . # これを追加! config.action_view.embed_authenticity_token_in_remote_forms = true endあとは書きまくるだけ!!
- 投稿日:2021-01-10T21:42:41+09:00
【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.rbclass User < ApplicationRecord has_one_attached :image ⬅️これを記述 〜以下略〜 end①ユーザーモデル
②レビュー投稿モデル
合計2つのモデルにhas_one_attached :image
を記述しました。そしてマイグレーションファイルから画像保存用の記述を削除して、db:migrateをする。
結果
record_typeというカラムにUserとPartyという2つの表示がされています。
大成功!(やっぱり会えたねActive Strage
気になったこと
ユーザーは複数枚の画像投稿(もしくは動画)が可能なように考えているのですが、もう1つテーブルを作ってそこに
has_many_attached
を使って保存されるのだろうか?
複数の場合はhas_one
からhas_many
に変わるとのことですがこのカラムを見ているとできそうな雰囲気も感じますが、実際にやってみた結果はまたこちらで記事を書こうと思います。以上、Active Strageで2つのテーブルから画像を保存する方法でした。
- 投稿日:2021-01-10T20:39:12+09:00
Railsでダブルポリモーフィック
Rails - Polymorphic Favorites (user can favorite different models)
https://stackoverflow.com/questions/21817019/rails-polymorphic-favorites-user-can-favorite-different-modelsDouble polymorphic association
https://stackoverflow.com/questions/15367098/double-polymorphic-associationRailsで中間テーブルダブルポリモーフィックやってみた。
https://qiita.com/kumewata/items/00e9f6994a9585d6542a
- 投稿日:2021-01-10T18:55:30+09:00
【個人開発】テストユーザーを募集するサイト作ってみた!
初めに
前、MENTAの作成者さんが動画で「webアプリは公開してからじゃないと使われるかわからない」って言っていたので
ベータ版専門のアプリ宣伝サイトをつくってみました。
そんなことサイトもまだベータ版でデザインは相当雑です。使われるようになってきたらデザインも変更しますし機能も追加します。URL:
https://testuser-b.herokuapp.com/テストユーザーはなぜ必要?
webアプリは公開しないと需要があるかわかりません。
「市場調査で調べられる」と思うかもしれないが口先だけ使うと言われただけで、公開したあとその人たちが本当に使うかはわからないんです。だから、本当に需要があるかどうかは実際に公開してみるしかないのですが
何か月もかけて開発したのに誰にも使われなかった...というのは悲しすぎます。
webアプリは当たる確率の方が低いです(6%程)だからヒットしなくてもダメージが少ないようベータ版を作ればいいのですが なかなか使ってもらうのは難しいです。webアプリは万全の状態で宣伝するものなので 今あるwebアプリの宣伝所はベータ版をなかなか受け入れてくれません。 そこでこのTest User βが生まれました。
使った技術
rails6
ruby2.7
heroku free(スリープしないよう設定している)開発時間は数時間
ロゴは自作
開発費用もサーバー代も無料です。まとめ
webアプリは公開してからじゃないと使われるかわからない
市場調査だけではわからない何がともあれ使ってみてください。お願いします(^_-)-☆
URL:
https://testuser-b.herokuapp.com/僕のツイッターもお願いします。
開発中に気づいたことをつぶやいてます。
https://twitter.com/Yuuki49079799
- 投稿日:2021-01-10T18:51:45+09:00
RailsでExtend(Concerns)を利用してControllerの処理を共通化する。
背景
記事一覧をPage#homeに出力したかった。
@ posts = Post.allのデータは、Postモデル、コントローラにある。Pageモデル、コントローラには無い。
PostのデータをPageに持ってくるためにはどうすればいいかググっていた。解決
この情報をPageに持って行きたい。
posts_controller.rbclass PostsController < ApplicationController def index @posts = Post.all end endPageに取り込む。
Page.rbclass Page < ApplicationRecord + extend Post::Models endPageでPostの情報を定義してみる。
pages_controller.rbdef 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
- 投稿日:2021-01-10T17:56:17+09:00
yarnがインストールされていないとのこと
rails new でアプリを作ろうとすると
RubyとRailsがインストールでき、アプリを作ってみようかとすると
yarnがインストールされていませんよと言われたので、yarnをインストールして解消。環境
Mac
Ruby2.6.0
Rails6.1.0rails 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年半ぶり?
ん、Railsのバージョンが変わってる?
yarnのインストールによってRailsのバージョンが6.1.0から6.1.1になったのでしょうか。$ rails -v Rails 6.1.1やはり変わっている。問題ないでしょう。知らんけど。
さて、Rubyの勉強を少し挟んで、Railsアプリの中身を作ることにします。参考書籍
- 投稿日:2021-01-10T17:29:14+09:00
Rubyで黒澤ルビィを作る(Ruby + LINE Messaging API)
黒澤ルビィ、可愛いですよね。
LINE上で喋るルビィちゃんを作りました。Rubyで。
以下の仕組みで動いています。
LINE Messaging API
を用いて、「黒澤ルビィ」としてLINEメッセージの受信、送信を行う。Ruby(Ruby on rails)
でLINE Messaging API
とやり取りするAPIサーバを作る。
- メッセージを形態素解析し、その結果をもとにマルコフ連鎖で新しく文章を生成する
LINEアカウントを作る
前準備としてルビィちゃんとして動くアカウント(厳密に言うと、Messaging APIのチャネル)が必要です。それをつくります。
LINE Developersから登録します。
Create a new channel
から新しく作ります。なお、作るときにどういうTypeか聞かれると思います。今回作りたいものはMessaging APIで作ることができるので、該当するものを選択してください。無事作成できると、アクセストークンを取得できます。
また、アカウントのアイコンや自己紹介欄などを設定できます。1APIサーバを作る
画面を持つ予定はないので、APIモードでRailsプロジェクトを作ります。
line-bot-api
gem2を入れます。先程作成したアカウントのアクセストークンを使って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 endWebhook URLを設定する
作成したAPIサーバをデプロイします。(今回はHerokuにデプロイしました。)
最初に作成したアカウントの設定画面でWebhook URLを設定し、メッセージがきたタイミングでLINEがリクエストを送ってくれるようにします。
LINEグループに追加します。いい感じにレスポンスしてくれます。
その他
- グループラインにルビィちゃんを混ぜたところ、「かわいい」「キメラが誕生した」「新しいおもちゃ」と非常に好評でした。
- クソデカ羅生門を覚えさせた人がいてめちゃくちゃになりました。
- グループラインにBotアカウントは一つまでしか入れられないようです。黒澤姉妹で話すことはできませんでした。
- 常に返信がくるとグループラインがやかましくなるので、ランダムで投稿したりしなかったりさせています。(ちなみに直接ルビィちゃんに話しかけると100%返信が来ます。)
- push_messageを使うとルビィちゃんから話しかけることもできます。
- 本当はアカウントを紹介したいのですが、記録した言葉の中に含まれる個人情報を考慮すると中々難しいものがあります。
- 今の所ルビィちゃん要素が0なので、ルビィちゃんっぽくさせたいです。
まとめ
LINE Messaging API
とRuby
を用いて黒澤ルビィを作りました。LINE Botは意外と簡単に作ることができるので、ぜひ作ってみてください。
開発用のアカウントも合わせて作ったりしています。ちなみに画像は友達が描いたやつです。 ↩
以下を参考にしました。Ruby on Rails で Yahoo のテキスト解析 APIを使う-スケ郎のお話 ↩
- 投稿日:2021-01-10T16:40:06+09:00
Railsチュートリアルで作ったサンプルアプリの「ファイルを選択」ボタンをかっこよくしてみた
環境
- Windows10 Home ver.2004
- ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x64-mingw32]
- Rails 6.1.0
はじめに
Railsチュートリアルの13.4.1章では、サンプルアプリに画像付きマイクロポスト投稿機能を追加するのだが、デフォルトだと画像を選ぶ際にクリックする「ファイルを選択」ボタンが
ダサい他のボタンとの統一感のなさのせいで目立ってしまう。
この状況をなんとかしたいと思い、色々試してみた。結果
デフォルトのボタンを非表示にした上で別のボタン「Choose an image」を用意し、そのボタン経由でデフォルトのボタンを押させることによって、機能を損なわずに見た目に統一感を出すことができた。
マウスオーバー処理も含め、下の「Post」ボタンのデザインをできる限り忠実に再現した。
もちろん、デフォルトの動作と同様に、画像を選択した場合はそのファイル名が表示される。
変更・追加したコードは次の通り。なお、今回は勉強のために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
を編集した。以上
[Rails][file_field]画像のアップロードのボタンデザインを変更する - Qiita(https://qiita.com/Yukina_28/items/4a8332354f6cb7c7a6f6) ↩
railsのfile_fieldで画像クリックによりアップロード - Qiita(https://qiita.com/zukakosan/items/41ed95fea2323cf458a9) ↩
- 投稿日:2021-01-10T16:00:06+09:00
【Ruby On Rails / HTML】ウェブページ表示時、カーソル(キャレット)を適当な場所へ合わせ表示する方法
備忘録です。
カーソルとキャレットとは
キャレット≒カーソル
マウスカーソルなどと区別したい場合にキャレットという言葉が用いられます。本題
カーソルを適当な場所へフォーカスするには、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> (後略)上記の場合、新規登録のウェブページを開いてみると、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> (後略)このように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
- 投稿日:2021-01-10T15:57:50+09:00
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-ymlversion: '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に接続するための記述 - dbDockerfile
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 installGemfile
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.ymldefault: &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まとめ
途中エラーが幾つか発生しましたが、DockerはもちろんRails,Mysqlを学ぶいい機会となりました。
次は本番環境(heroku)へのデプロイもまとめます。
- 投稿日:2021-01-10T15:32:40+09:00
バックエンドの設計を任されたので、エンドポイント設計をしっかり学んでみる
はじめに
こんにちは、大学を休学してスタートアップでWebエンジニアとして働いてる、おにかんです。
現在、関わっているプロジェクトのエンドポイント設計を任されたので良い機会だと思い、1から勉強して、まとめてみました。何かご指摘等ありましたらコメントしていただけるとありがたいです。
エンドポイント設計を始めよう!
*そもそもエンドポイントとは?
APIにアクセスするためのURIという認識で問題ないかと思います。
基本的な考え方
URI設計は「シンプルでわかりやすい」ものであるべきです。なぜ「シンプルでわかりやすい」URIが良いのでしょうか?主な理由に「開発者のミスを減らすことができる」ということが挙げられます。例えば、下記の二つのURI、どちらがわかりやすいでしょうか?
api/v1/p/capi/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/postsGETの時にあった、id指定がなくなっているのが確認できるかと思います。つまり、
Posts
に属する形でリソースが作られるということです。基本的に、POSTは「新規作成」に使われるべきであり、既存のリソースに対して何かしらの操作をしたい場合は、PUT
やDELETE
を使うと良いと思います。PUT
PUTメソッドは情報を「更新」される場合に使われます。PUTメソッドはPOSTと違って、更新したいURIを指定します。下記は例です。(実際にはクエリパラメータでデータを指定したりするのですが、今回は割愛)
PUT api/v1/posts/100POSTメソッドと違って、直接指定しているのが確認できると思います。(POSTは従属するリソースを作成する。)
ちなみに指定したリソースが存在しない場合、リソースの作成が行われます。しかし、リソースの作成には基本的にPOSTメソッドを使うべきです。DELETE
DELETEメソッドは指定したURIのリソースの「削除」を行います。
DELETE api/v1/posts/100PATCH
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
情報を与えています。基本的な部分は以上です。あとは実戦でフィードバックをもらいながらやっていくのがいいのかなと思います。
ではでは〜
- 投稿日:2021-01-10T11:39:07+09:00
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:createGemfile
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 authconfig/initializers/cors.rbRails.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 endconfig/initializers/devise_token_auth.rbDeviseTokenAuth.setup do |config| config.change_headers_on_each_request = false config.token_lifespan = 2.weeks config.token_cost = Rails.env.test? ? 4 : 10 endUserモデル
app/models/user.rbclass User < ActiveRecord::Base devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable include DeviseTokenAuth::Concerns::User endrails db:migrate
ルーティング
config/routes.rbRails.application.routes.draw do namespace :v1 do mount_devise_token_auth_for "User", at: "auth" end endAPI動作確認
今回はPostmanを使用します。
Devise_token_authの使い方は下記URLを参考にします。
https://devise-token-auth.gitbook.io/devise-token-auth/usageサインアップ
HTTPメソッド:
POST
URL:http://localhost:3000/v1/auth/
Body:password
サインイン
HTTPメソッド:
POST
URL:http://localhost:3000/v1/auth/sign_in
Body:password
サインアウト
HTTPメソッド:
DELETE
URL:http://localhost:3000/v1/auth/sign_in
Body:uid
,access-token
,client
サインイン処理した際、Headersタブに必要なパラメータを確認することができます。
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画面
タスク機能追加
rails g controller v1/tasks rails g model Task
モデルの追加
db/migrate/YYYYMMDD_create_tasks.rbclass 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 endrails db:migrate
ルーティング
config/routes.rbRails.application.routes.draw do namespace :v1 do resources :tasks mount_devise_token_auth_for 'User', at: 'auth' end endコントローラー
app/controllers/v1/tasks_controller.rbclass 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 endfrontend/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画面
GitHub
https://github.com/yodev21/devise_token_auth_app終わりに
フロントエンドとバックエンドを分けた際のログイン機能がついたCRUDを実装してみて
ハマるポイントが多数あり学ぶきっかけになりました。まだまだ改善点や試したいことがあるため、今後は下記を中心に学んでいきたいと思います。
・ 複数テーブルをまたぐデータ取得機能
・ Vue Routerの導入
・ Vuexの導入参考記事
- 投稿日:2021-01-10T10:00:26+09:00
【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以下のビューファイルで読み込むのと処理速度がかなり違う印象。
- 投稿日:2021-01-10T01:31:51+09:00
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 installRSpecの設定をする
ターミナルで以下のとおり入力して、ディレクトリとファイルを生成します。
ターミナル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
というグループの中に記述してからインストールします。Gemfilegroup :development, :test do gem 'factory_bot_rails' endターミナルbundle install簡単な紹介になりますが、FactoryBotは以下のように記述して使います。※アプリにUsersテーブルがあると想定
spec/factories/users.rbFactoryBot.define do factory :user do name {"sample"} email {"sample@mail"} password {"sample777"} end endこのように記述することで、テストで使用するユーザーの情報を事前に用意しておくことができます。
Fakerとは
ランダムな値を生成するgemで,メールアドレス、人名、パスワードなどのランダムな値を生成してくれます。
RSpecと同様にGemfileのgroup :development, :test
というグループの中に記述してからインストールします。Gemfilegroup :development, :test do gem 'faker ' endターミナルbundle installFakerはこんな感じでランダムな値を生成します。
spec/factories/users.rbFactoryBot.define do factory :user do name { Faker::Name.initials(number: 5) } email { Faker::Internet.free_email } password { Faker::Internet.password(min_length: 6) } end endFactoryBotとFakerでできること
テストコードを実行するたびにランダムな値でインスタンスを生成することができます。
テストをするアプリにユーザー管理機能がある場合、emailなどのカラムをテーブルに用意されていると思いますが、複数のテストを行う際に、「すでにemailの重複したインスタンスが存在する」といったような意図しない形でテストエラーが出てしまうことを防ぐのに役立ちます。今回はここまでで、次の記事でRSpecを使った単体テストについて取り上げたいと思います。
参考資料
RSpecの公式GitHub
FactoryBot(factory_bot_rails)の公式GitHub
Fakerの公式GitHub