20210809のRailsに関する記事は19件です。

【条件分岐】商品が売却されていたら処理をする方法

今回覚えたことをメモします。 ◇実装内容 商品購入機能後の条件分岐 ◇結論◇ ビューファイルに、「item.purchase」と記述をすると「商品に紐づく購入履歴」という形で処理をしてくれた。 そこにif文を加えて「もし、その商品を購入していたら〜処理〜」とうい記述ができた。 ※モデル名で名前は変わる。 ↓は「もし、その商品を購入していたらSold Out!!を表示させる」 index.html.erb <% if item.purchase %> <div class='sold-out'> <span>Sold Out!!</span> </div>  <% end %> ◇前提◇ 2つはアソシエーションが組まれている 商品情報:itemモデル 購入履歴:purchaseモデル  カラム:user_id     :item_id ◇別の例◇ ↓コントローラーに記述。before_actionメゾットで編集ページは商品が購入されたら、root_pathへ戻る形になる。 items_controller.rb before_action :item_purchase ,only: :edit def edit @item = Item.find(params[:id]) end def item_purchase @item = Item.find(params[:id]) if @item.purchase redirect_to root_path end end 本日は以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

unicornの自動起動(ubuntu20.04でのinit.d)

unicornをOS起動時に自動起動させたい 環境 Ubuntu20.04 rails6 unicorn nginx 設定 以下参考にしたところ Unicorn の自動起動と etc/init.dの使い方 ここにあるshellスクリプトをunicornというファイル名で/etc/init.dに置きます。 chmodは755に update-rc.d ubuntu20.04の場合、chkconfigコマンドが入っていないので update-rc.dで自動起動の設定をします。 そのためシェルスクリプトにrun levelを書いて置く必要があります。 (以下の### BEGIN INIT INFOから### END INIT INFOの部分) これを記述しておかないとupdate-rc.dが機能しません。 /etc/init.d/unicorn #!/bin/sh ### BEGIN INIT INFO # Provides: myrec # Required-Start: $all # Required-Stop: # Default-Start: 2 3 4 5 # Default-Stop: # Short-Description: unicorn shell ### END INIT INFO USER="ec2-user" NAME="Unicorn" ENV="production" ROOT_DIR="/var/www/application" PID="${ROOT_DIR}/tmp/pids/unicorn.pid" CONF="${ROOT_DIR}/config/unicorn.rb" OPTIONS="" CMD="RAILS_SERVE_STATIC_FILES=1 unicorn_rails -c ${CONF} -E ${ENV} -D" start() { if [ -e $PID ]; then echo "$NAME already started+${PID}" exit 1 fi echo "start $NAME" su - ${USER} -c "cd ${ROOT_DIR} && ${CMD}" } stop() { if [ ! -e $PID ]; then echo "$NAME not started" exit 1 fi echo "stop $NAME" kill -QUIT `cat ${PID}` } force_stop() { if [ ! -e $PID ]; then echo "$NAME not started" exit 1 fi echo "stop ${NAME}" echo ${PID} kill -INT `cat ${PID}` } reload() { if [ ! -e $PID ]; then echo "$NAME not started" start exit 0 fi echo "reload $NAME" ##kill -HUP `cat ${PID}` su - $USER -c "cd ${ROOT_DIR} && bundle exec unicorn_rails:reload" ## su $USER -lc "cd ${RAILS_ROOT}&& bundle exec rake unicorn_rails:reload" } restart() { force_stop sleep 5 start } case "$1" in start) start ;; stop) stop ;; force-stop) force_stop ;; reload) reload ;; restart) restart ;; *) echo "Syntax Error: release [start|stop|force-stop|reload|restart]" ;; esac OS起動時に自動起動 chkconfigはubuntuでは使えないので、以下のコマンドを投入 sh $ sudo update-rc.d unicorn defaults このコマンドは/etc/rc*.d/(*はrun level)にシンボリックリンクを張ります。 これでOS起動時にunicornが起動します。 確認 sh $ ls -l /etc/rc*.d | grep unicorn 自動起動をやめたいとき sh $ sudo update-rc.d unicorn remove
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Herokuデプロイしたらレイアウトが総崩れした

忘れないための備忘録! 今回Herokuにデプロイした時に 開発環境側で出来ていたレイアウトを確認したら レイアウトがぐちゃぐちゃになっていて 唖然としました。 Heroku上で動作する環境の仕様やアセットパイプラインの挙動が異なるため見たいです。 野呂さんが自分が直面している問題の解決法を載せていてくださったので 試してみたらすぐ解決。 Rails初学者がつまずきやすい「アセットパイプライン」 Heroku上に反映するためには、2つの作業をすることが確実な方法。 ・本番環境上でアセットパイプラインを通るようにプリコンパイル処理を実行する ・本番環境上でアセットパイプラインを自動で通るように設定を変更する Herokuにファイルを送付する前に $ rake assets:precompile RAILS_ENV=production を開発環境上のコンソールで実行します。 config/production.rbファイル内の記述のうち config.assets.compile = false の記述を見つけ、「falseをtrueに書き換えて」保存する。 これで、Heroku上で使われる環境上のHTMLにCSSやJavaScriptを反映させることができる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails] 自作のアクションのルーティング設定(memberとcollection)

はじめに 検索機能の実装過程で新しい気づきがあったので記事に残しておきます。 よろしくお願いします。 自作のアクション ここではRailsの7つのアクションとされるもの以外に、自分で作ったアクションのことを「自作のアクション」と呼んでいます。 今回検索機能を実装するにあたって、新たにコントローラーに「search」アクションというものを作成しました。 def search @tweets = Tweet.search(params[:keyword]) end コントローラーの中にこんな感じで記述しました。 モデルの方でこの中で使われているsearchメソッドは定義されていますが、今回は特に説明はしません。 memberとcollection memberとcollectionはルーティングを設定するときに使用できる、URLや実行されるコントローラーを任意にカスタムできるものです。 例えばmemberを使ってルーティングを設定すると下記のようになります。 Rails.application.routes.draw do resources :tweets do member do get 'search' end end end rails routesすると↓ Prefix Verb URI Pattern search_tweet GET /tweets/:id/search(.:format) tweets#search collectionを使用すると下記のようになります。 Rails.application.routes.draw do resources :tweets do collection do get 'search' end end end rails routesでルーティング確認すると↓ Prefix Verb URI Pattern search_tweets GET /tweets/search(.:format) tweets#search この二つの違いは、URLの指定先に:idが含まれているか否かになります。 memberは:idあり。 collectionは:idなし。 終わりに 実装途中にこれ大事だ!と思い取り急ぎ記事にしました。 実際に実装した検索機能の手順は追って記事にしたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails の wrap_parameters は、controller からモデルを推測できる場合そのモデルにあるプロパティしか受け付けられない

はじめに Rails の wrap_parameters の動きに想定外なところがあってハマりました。原因が分かったので、他の人が同じところにハマらないように、共有します。 先に結論 wrap_parameters の処理は、controller からモデルが推測された場合、そのモデルにあるカラム のみ ラッパーする。 # Post モデルに title と body カラムのみとする(created_at, updated_at もあるが本題と関係ない) # controller で下記のコードがある場合 def post_params params.require(:post).permit(:title, :body, :auth_code) end # Post モデルに auth_code カラムがないため、実際のリクエストに auth_code 項目が送信された場合は、下記のようになる # オリジナルパラメータに auth_code 項目ありますが、ラッパーされた部分には auth_code 項目がありません!!! ... Processing by PostsController#create as JSON Parameters: {"title"=>"hello world", "body"=>"最初の記事", "auth_code"=>"[FILTERED]", "post"=>{"title"=>"hello world", "body"=>"最初の記事" } ... 再現してみる まずベース部分を用意しておく $ rails -v Rails 6.1.3.2 $ ruby -v ruby 2.7.3p183 (2021-04-05 revision 6847ee089d) [x86_64-darwin20] $ rails new wrap-params $ cd wrap-params $ rails g scaffold post title body $ rails db:migrate $ rails s ここまでできたら、下記 curl コマンドが正常に投稿できるはずです。 $ curl -X POST -H 'Content-Type: application/json' localhost:3000/posts.json -d '{"title":"title1","body":"body1"}' {"id":3,"title":"title1","body":"body1","created_at":"2021-08-08T11:29:18.659Z","updated_at":"2021-08-08T11:29:18.659Z","url":"http://localhost:3000/posts/3.json"} auth_code を追加してみる 仮に Post を投稿する際に認証コードを同時に入力しなければならない 要件が来たとします。 認証コードの検証処理は本題と関係ない為、スルーします PostController の post_params メソッドに :auth_code を追加して、 create または update アクションで post_params[:auth_code] で取得してみると、ずっと空になっています。 def create puts "auth_code: #{post_params[:auth]}" @post = Post.new(post_params) ... end ... def post_params params.require(:post).permit(:title, :body, :auth_code) end # auth_code を追加してリクエストする $ curl -X POST -H 'Content-Type: application/json' localhost:3000/posts.json -d '{"title":"title1","body":"body1","auth_code":"123456"}' # Rails の log Started POST "/posts.json" for ::1 at 2021-08-09 20:17:20 +0900 (0.1ms) SELECT sqlite_version(*) Processing by PostsController#create as JSON Parameters: {"title"=>"title1", "body"=>"body1", "auth_code"=>"123456", "post"=>{"title"=>"title1", "body"=>"body1"}} Can't verify CSRF token authenticity. auth_code: TRANSACTION (0.0ms) begin transaction ↳ app/controllers/posts_controller.rb:31:in `block in create' Post Create (0.7ms) INSERT INTO "posts" ("title", "body", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["title", "title1"], ["body", "body1"], ["created_at", "2021-08-09 11:17:20.465628"], ["updated_at", "2021-08-09 11:17:20.465628"]] ↳ app/controllers/posts_controller.rb:31:in `block in create' TRANSACTION (0.8ms) commit transaction ↳ app/controllers/posts_controller.rb:31:in `block in create' Rendering posts/show.json.jbuilder Rendered posts/_post.json.jbuilder (Duration: 0.2ms | Allocations: 111) Rendered posts/show.json.jbuilder (Duration: 0.5ms | Allocations: 292) Completed 201 Created in 9ms (Views: 1.3ms | ActiveRecord: 1.7ms | Allocations: 4807) 実行結果は、オリジナルの parmas に auth_code がありますが、wrap_parameters でラッパーされている部分には auth_code 項目がない!!! 原因 wrap_parameters のデフォルトの設定の場合は、 controller 名からモデルクラスを推測してみている # https://github.com/rails/rails/blob/main/actionpack/lib/action_controller/metal/params_wrapper.rb#L156-L172 def _default_wrap_model return nil if klass.anonymous? model_name = klass.name.delete_suffix("Controller").classify begin if model_klass = model_name.safe_constantize model_klass else namespaces = model_name.split("::") namespaces.delete_at(-2) break if namespaces.last == model_name model_name = namespaces.join("::") end end until model_klass model_klass end モデルクラスが推測できたら、 attribute_names メソッドが定義されている、かつ、値が空ではない場合、その値を include に設定する # https://github.com/rails/rails/blob/main/actionpack/lib/action_controller/metal/params_wrapper.rb#L109-L127 if m.respond_to?(:attribute_names) && m.attribute_names.any? self.include = m.attribute_names if m.respond_to?(:stored_attributes) && !m.stored_attributes.empty? self.include += m.stored_attributes.values.flatten.map(&:to_s) end if m.respond_to?(:attribute_aliases) && m.attribute_aliases.any? self.include += m.attribute_aliases.keys end if m.respond_to?(:nested_attributes_options) && m.nested_attributes_options.keys.any? self.include += m.nested_attributes_options.keys.map do |key| (+key.to_s).concat("_attributes") end end self.include end wrap_pamameters の include オプションがある場合、その内容だけラッパーするようになっています 上記によって、モデルクラスにあるプロパティだけラッパーされるようになるわけです。 まとめ 簡単な処理だと思っていた wrap_parameters は、意外にいろいろやっていますね。 補足 controller の post_params メソッドの処理にそのまま auth_code を追加するところがキレイじゃないとのご意見があると思いますが、ごもっともです。 ただ、「設計しすぎないように」するため、今回の例ですと Post モデルにない項目が本当に auth_code しかないので、API のみの場合最終的に下記になるのでこれで良いかと思います。 Post モデルにない項目または処理がもっと増えたら、FormObject を作るほうがキレイだと思います。 def create unless validate_auth_code?(params[:auth_code]) head :bad_request return end @post = Post.new(post_params) if @post.save render :show, status: :created, location: @post else render json: @post.errors, status: :unprocessable_entity end end 参考リンク attribute_names を使うようにした commit
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RSpecでauthenticate_userを突破する

前提 ・以下にアクセスできるかテストしたい ・アクセス先でcurrent_userを用いた処理をしているため、サインインした状態でいたい hotel GET /hotels/:id(.:format) hotels#show やっていこう #spec/rails_helper.rb RSpec.configure do |config| # テスト時にdeviseのヘルパーを呼び出すことができる => sign_inが使えるになる config.include Devise::Test::IntegrationHelpers # gemのベースであるwardenのプロキシサーバーのエラーが起こるための対処法 config.include Devise::Test::ControllerHelpers end #spec/controllers/hotels_controller_spec.rb require 'rails_helper' RSpec.describe HotelsController, type: :controller do describe "GET #show" do before do @user = FactoryBot.create(:user) end it "returns http success" do # 先ほどdeviseのヘルパーを呼び出せるようにしたため使える sign_in @user # get :show, id: @user.id だとidを渡せなかった get :show, params: { id: @user.id } expect(response).to have_http_status(:success) end end end 参考記事 Rails RSpec コントローラーテストでログイン状態にする 【Rspec】Devise::MissingWardenエラーの解決策 Rails5の勉強: unknown keyword: idについて
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails 6 Herokuデプロイ時のエラー(Precompiling assets failed.)

Herokuデプロイ中に起きたエラー methods since the "loose" mode option was set to "true" for @babel/plugin-proposal-class-properties. The "loose" option must be the same for @babel/plugin-proposal-class-properties, @babel/plugin-proposal-private-methods and @babel/plugin-proposal-private-property-in-object (when they are enabled): you can silence this warning by explicitly adding ["@babel/plugin-proposal-private-methods", { "loose": true }] to the "plugins" section of your Babel config. Though the "loose" option was set to "false" in your @babel/preset-env config, it will not be used for @babel/plugin-proposal-private-methods since the "loose" mode option was set to "true" for @babel/plugin-proposal-class-properties. The "loose" option must be the same for @babel/plugin-proposal-class-properties, @babel/plugin-proposal-private-methods and @babel/plugin-proposal-private-property-in-object (when they are enabled): you can silence this warning by explicitly adding ["@babel/plugin-proposal-private-methods", { "loose": true }] to the "plugins" section of your Babel config. Though the "loose" option was set to "false" in your @babel/preset-env config, it will not be used for @babel/plugin-proposal-private-methods since the "loose" mode option was set to "true" for @babel/plugin-proposal-class-properties. The "loose" option must be the same for @babel/plugin-proposal-class-properties, @babel/plugin-proposal-private-methods and @babel/plugin-proposal-private-property-in-object (when they are enabled): you can silence this warning by explicitly adding ["@babel/plugin-proposal-private-methods", { "loose": true }] to the "plugins" section of your Babel config. ! ! Precompiling assets failed. ! ! Push rejected, failed to compile Ruby app. ! Push failed [Precompiling assets failed.] 参考記事 Rails6系を使用している時に、 herokuへのアプリケーションデプロイでPrecompile assets failedになることがあるっぽい よくみてみると使用している Bootstrapのバージョンが5系の時は $ yarn add popperjs/core を実行してから 再度デプロイをすることで、問題なくcompileができるようになるっぽい。 ちなみに使用しているBootstrapが4系の時は $ yarn add popper.js を実行すれば問題ないかも。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スケーラブルなフロントエンド開発 on Rails

はじめに Rails オンリーでのフロントエンド開発は、Vue.js や React と比べると、型安全が得られないこと、コンポーネント指向ではない等の理由からスケールしづらく、大規模開発には向いていない、と考えていました。 一方で、SPA を使う理由が特に無い場合、Rails オンリーでの開発にはそれなりにメリットがあるとも考えていました。 技術要素が少なくて済むこと、ActionView の恩恵を受けて爆速で開発できること等です。 本記事では、Rail には乗りつつ、スケーラブルにフロントエンド開発をするにはどうすればいいかということに焦点を当てて、いくつかの提案をします。 まず結論 CSS を可能な限り書かない → Tailwind on Rails どうしても CSS を使いたいときは Scoped に書く views / components / FormHelper の3レイヤーで考える グローバルな CSS は 基本的な HTML 要素に限り使用可とする ViewComponent を使用する CSS を可能な限り書かない CSS はグローバルに定義される定数のようなものです。どのファイルからでもアクセスできますが、それはつまりリファクタリングすると、その影響が画面全体に及ぶということです。 そのため、クラス名やファイルの管理の仕方を事前に十分に設計しておく必要があります。 開発が始まってからも、そのルールを開発者に周知し、守っていくという運用が発生してしまいます。 CSS を使わなければ上記を考えなくて済みます。Tailwind CSS を使いましょう。 Tailwind CSS とは Tailwind CSS は最近流行っている CSS ライブラリです。 CSS をクラスで指定できます。私の経験上、実際に開発で使用する CSS の 99% は置き換え可能です。 Tailwind CSS を使用すれば、前述したような CSS の管理から開放されます。 それだけではなく、スタイルを html 上で表現できるというメリットも得られます。 最初は冗長に見えるかも知れないですが、CSS ファイルをわざわざ見に行かずともどの要素にどのスタイルが当たっているかが一目瞭然であるというメリットは、一度体験すると手放せなくなります。 下記の2つの例は等価です。 Tailwind CSS を使わない場合の erb + CSS <div class="tweet-index-page"> <div class="tweet-index-title">Tweets</div> <div class="tweet-form-container"> <%= render(TweetFormComponent.new(tweet: @tweet)) do %> <div class="text-blue-400"><%= notice %></div> <% end %> </div> <div class="tweet-list-container"> <% @tweets.each do |tweet| %> <%= render(TweetComponent.new(tweet: tweet))%> <% end %> </div> </div> .tweet-index-page { width: 20rem; height: 100vh; padding-top: 1.25rem; padding-bottom: 1.25rem; display: flex; flex-direction: column; } .tweet-index-title { padding-top: 1.25rem; padding-bottom: 1.25rem; font-size: 1.5rem; line-height: 2rem; font-weight: 700; } .tweet-form-container { position: sticky; top: 0px; --tw-bg-opacity: 1; background-color: rgba(255, 255, 255, var(--tw-bg-opacity)); padding-bottom: 0.75rem; } .tweet-list-container { display: flex; flex-direction: column; --tw-space-y-reverse: 0; margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse))); margin-bottom: calc(0.75rem * var(--tw-space-y-reverse)); overflow-y: scroll; } Tailwind CSS を使った場合の erb <div class="w-80 h-screen py-5 flex flex-col"> <div class="py-5 text-2xl font-bold">Tweets</div> <div class="sticky top-0 bg-white pb-3"> <%= render(TweetFormComponent.new(tweet: @tweet)) do %> <div class="text-blue-400"><%= notice %></div> <% end %> </div> <div class="flex flex-col space-y-3 overflow-y-scroll"> <% @tweets.each do |tweet| %> <%= render(TweetComponent.new(tweet: tweet))%> <% end %> </div> </div> Tailwind css のセットアップ 私はこちらの方法でセットアップして上手く動かすことができました。日本語で解説した記事もあるのでご参考にどうぞ。 CSS を書かざるを得ないとき 一部の CSS は Tailwind で表現できないので、生の CSS を書かざるを得ない状況もあるでしょう。 そのような場合は、ファイルパススコープの使用を推奨します。 ファイルパススコープとは Rails の CSS にスコープを持たせる ファイルパススコープは上記の記事で提唱されている概念です。公式にそのような名称が存在するわけではないようですが、非常に有用なアイデアだと思います。 下記のように、一番外側の要素に data-scope-path="[ファイルパス]" という属性を付与することで、CSS のスコープを限定するという方法です。 app/views/users/show.html.erb <div data-scope-path="users/show"> <div class="user-name"> <%= @user.name %> </div> </div> app/assets/stylesheets/scopes/users/show.scss [data-scope-path="users/show"] { .user-name { // ここに書いた CSS は users/show.html.erb のみに適用される } } これにより、Vue.js の scoped CSS のような、特定のファイルだけにスコープを限定した CSS を擬似的に再現できます。 この CSS ファイルを変更しても、影響範囲は特定のファイル内だけに限定されるので、安全なリファクタリングが可能になります。 views / components / FormHelper の3レイヤーで考える Atomic Design コンポーネント指向開発の文脈でよく登場する Atomic Design という概念があります。 ここでは詳細は割愛しますが、要するに画面を構成する要素を粒度や役割ごとにいくつかのレイヤーに分割しようという考え方です。 下記の図は5つの階層での分割を表しています。 Rails における対応 Rails においても、Atomic Design の考え方を適用できます。 Rails が持つ機能は下記のように適用できると考えられます。 Atoms はコンポーネント分割における最小単位で、Rails では ActionView が提供するtext_field options_for_select などの FormHelper のメソッド群がこれに対応すると考えることができます。 Molecules / Organisms / Templates は Atoms を組み合わせてできるより大きな単位のコンポーネントです。Rails では、ViewComponent で表現できます。 Pages は最も上位に位置するコンポーネントです。views/users/show.html.erb` のような従来の views ディレクトリ内のページファイルがこれに相当します。 ViewComponent → FormHelper の順で解説します。 ViewComponent View Components は Rails 6.1 からデフォルトでサポートされている機能です。開発元は React にインスパイアされたと語っており、従来の partial をコンポーネント志向の文脈で再解釈・拡張したと言えるでしょう。 セットアップは非常に簡単です。公式を参考にどうぞ。 rails generate コマンドに対応しているので、下記のようにファイルを生成できます。 $ bin/rails generate component Example title components/example_component.rb class ExampleComponent < ViewComponent::Base def initialize(title:) @title = title end # このコンポーネント関連のメソッドを自由に定義できる end components/example_component.html.erb <!-- example_component.rb のメソッドを自由に使える --> <span title="<%= @title %>"><%= content %></span> コンポーネントを呼び出す側では、下記のように記述します。 views/home/index.html.erb <%= render(ExampleComponent.new(title: "my title")) do %> Hello, World! <% end %> ViewComponent × Tailwind CSS 実装例 画像の水色のカード部分の実装です。 tweet_component.erb.html <div class="bg-blue-100 rounded p-3 flex flex-col justify-between space-y-2 "> <%= link_to(@tweet) do %> <pre class="text-lg"><%= @tweet.content %></pre> <% end %> <div class="flex flex-row justify-end space-x-3"> <div><%= button_to 'Edit', edit_tweet_path(@tweet), method: :get, class: "btn btn-white" %></div> <div><%= button_to 'Delete', @tweet, method: :delete, data: { confirm: 'Are you sure?' }, class: "btn-white" %></div> </div> </div> tweet_component.rb class TweetFormComponent < ViewComponent::Base def initialize(tweet:) @tweet = tweet end end partial との違い partial との違いとして、テストが書きやすいこと、パフォーマンスが優れていることがよく挙げられているようです。 下記の記事でそれぞれ分かりやすく書かれているので参考にどうぞ。 ViewComponent を試してみた 【Rails】ViewComponentとPartialのパフォーマンスを比較 個人的には、ViewComponent では、 各コンポーネント専用のメソッドを記述するファイルが用意されていること prop (引数)として渡すオブジェクトを自由に設定できるという点 に好感を持ちました。 partial では引数や命名がファイル名に制限されてしまう ので、ViewComponent の方がより柔軟なコンポーネント分割が可能になります。 Vue.js や React などのコンポーネント指向フレームワークっぽいインターフェースに似ており、非常に使いやすいと感じました。 FormHelper FormHelper は皆さんご存じの通り、基本的な要素を簡単に表示するための便利なメソッド群です。 label(:article, :title) # => <label for="article_title">Title</label> text_field(:article, :title) # => <input type="text" id="article_title" name="article[title]" value="#{@article.title}" /> AtomicDesign における Atoms はアプリケーションの中で繰り返し頻繁に使われる要素であるため、 1. シンプルで使いやすいインターフェースであること 2. アプリケーションのテーマに沿ったスタイルであること が求められます。 このうち、1 に関しては FormHelper が提供しているインターフェースが該当します。 一方で、2 で要求されているスタイルを FormHelper ごとに保持するような昨日は提供されていません。 言い換えると、FormHelper に統一されたスタイルを簡単に当てることができれば、十分に Atoms コンポーネントであるということが言えそうです。 どうするか グローバル CSS で要素ごとにスタイル定義をすることで解決できます。 scoped CSS を使おうと主張したばかりなのに、矛盾しているじゃないか!!と思った方もいると思いますが、ちゃんと下記のような理由があります。 基本的な HTML 要素のスタイルはプロジェクト全体で共有する必要がある。 基本的な HTML 要素は種類が限られているため、管理の手間が爆発的に増えることはない。 ViewComponent 内にスタイルを定義することも可能であるが Atoms コンポーネントの実装には向いていない。(インターフェースの利便性は FormHelper が圧倒的に優れているため) これらの理由から、FormHelper メソッドには例外的にグローバル CSS の使用を認めて良いと私は考えます。 ただし、ちょっとした注意点もあります。原則クラス指定のみにしてください。 要素指定だと影響範囲が広く、意図しない要素にまでスタイルがついてしまう可能性が高いためです。 Tailwind CSS を使った実装例 画像の Tweet 作成部分のフォームの実装です。 tailwind の @apply メソッドを使っています。SCSS ファイル内でも Tailwin のクラス名を用いてスタイルを記述できる方法です。 app/javascript/stylesheets/core-components.scss .btn { @apply rounded p-1 cursor-pointer; } .btn-white { @apply btn bg-white; } .btn-outline-blue { @apply bg-white border border-blue-500 border-solid; } .textarea { @apply border border-blue-500 border-solid rounded; } tweet_form_component.erb.html <%= form_with(model: @tweet) do |form| %> <% if @tweet.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(@tweet.errors.count, "error") %> prohibited this @tweet from being saved:</h2> <ul> <% @tweet.errors.each do |error| %> <li><%= error.full_message %></li> <% end %> </ul> </div> <% end %> <%= form.label :content, hidden: true %> <%= form.text_area :content, class: "base-textarea w-full" %> <div class="flex flex-col space-y-1"> <div><%= content %></div> <%= form.submit class: "btn btn-outline-blue" %> </div> <% end %> ※ @apply メソッドは node_modules に依存しているので、core-components.scss を app/javascript/stylesheets/ ディレクトリ配下に置く必要があることに注意してください。app/assets/stylesheets/ 配下では動きません。 もう一度結論 CSS を可能な限り書かない → Tailwind on Rails どうしても CSS を使いたいときは Scoped に書く views / components / FormHelper の3レイヤーで考える グローバルな CSS は 基本的な HTML 要素に限り使用可とする ViewComponent を使用する Rails でも、CSS を使わず、コンポーネント分割を意識することで、スケールするフロントエンド開発ができそうです。 部分的にでも皆さんの開発の参考になればと思います。 参考 この記事の発表用に作った資料 この記事の実装例として作ったサンプルアプリケーションのソースコード
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FactoryBotを用いて紐づいたユーザーの登録

User:Hotel = 1:多 Hotelインスタンスは生成時にUserのIDがないと作成できません。 そのため、user_idを直接書き込んで登録しようとしましたが、エラーになりました。 上手くいかない原因は、紐付く親が存在していないからではないかと以下の記事のコメント欄には書いてありました。 rspecでエラーが出ます 誤った書き方 # spec/factories/hotels.rb FactoryBot.define do factory :hotel do user_id {1} name {"ホテル名"} address1 {"東京都"} address2 {"千代田区XXX町X-X-X"} end end # spec/models/hotel_spec.rb require 'rails_helper' RSpec.describe Hotel, type: :model do describe "ホテル" do context "保存" do it "正しく登録" do hotel = FactoryBot.build(:hotel) expect(hotel).to be_valid end end end end 解決方法 user_idを登録する際に、Userモデルを作成した際のFactoryBotを活用 # spec/factories/users.rb FactoryBot.define do factory :user_bob, class: "User" do name {"bob"} email {"bob@sample.com"} password {"sample"} password_confirmation {"sample"} end end 解決1 user_idの登録時に、userインスタンスの作成 # spec/factories/hotels.rb FactoryBot.define do factory :hotel do user_id { FactoryBot.create(:user_bob).id } name {"ホテル名"} address1 {"東京都"} address2 {"千代田区XXX町X-X-X"} end 解決2 associationを使用する。 # spec/factories/hotels.rb FactoryBot.define do factory :hotel do # factory名はmodel名と同一の場合、省略可 association :user, factory: :user_bob name {"ホテル名"} address1 {"東京都"} address2 {"千代田区XXX町X-X-X"} end 参考記事 Rspecのmodelテストで、user_id: nil, post_id: nilになってしまう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails の Dockerイメージを軽量化してみる

鉄板すぎる話ですが、自分の手元で確かめたかったのでメモに残します。 Alpine Linux を利用する RUN 命令などを削減する マルチステージビルド 全部盛り 何もしない状態 Docker ドキュメント を参考に作成。 FROM ruby:2.6.8 RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs RUN mkdir /app WORKDIR /app COPY Gemfile /app/Gemfile COPY Gemfile.lock /app/Gemfile.lock RUN gem install bundler:2.2.16 RUN bundle install COPY . /app COPY dockerfiles/rails/entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 CMD ["rails", "server", "-b", "0.0.0.0"] サイズは1GB超え。 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE vocabook_api_app latest 005fb8bfbdae 4 minutes ago 1.11GB Alpine Linux を利用する Alpine Linux は、コンテナでよく登場する軽量な Linux ディストリビューション。 apk というパッケージマネージャを利用するため、サブコマンドやパッケージ名を修正。 FROM ruby:2.6.8-alpine3.14 RUN apk update -qq && apk add --no-cache build-base postgresql-dev nodejs RUN mkdir /app WORKDIR /app COPY Gemfile /app/Gemfile COPY Gemfile.lock /app/Gemfile.lock RUN gem install bundler:2.2.16 RUN bundle install COPY . /app COPY dockerfiles/rails/entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 CMD ["rails", "server", "-b", "0.0.0.0"] 何もしない状態から 300MB 削減。 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE vocabook_api_app latest 24268673b3c8 43 seconds ago 799MB RUN 命令などを削減する RUN や COPY などの命令によってレイヤと呼ばれる中間イメージが作成される。 命令数を減らすことで生成されるレイヤを削減し、イメージの軽量化を図る。 FROM ruby:2.6.8 RUN apt-get update -qq && \ apt-get install -y build-essential libpq-dev nodejs && \ mkdir /app WORKDIR /app COPY Gemfile Gemfile.lock /app/ RUN gem install bundler:2.2.16 && bundle install COPY . /app COPY dockerfiles/rails/entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 CMD ["rails", "server", "-b", "0.0.0.0"] この程度の命令数ではサイズは変わらず。 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE vocabook_api_app latest 3b3100aef7b1 About a minute ago 1.11GB マルチステージビルド bundle installで分けている記事があったので参考に試してみる。 パッケージをインストールするステージ、そのパッケージをコピーして持ってくるステージに分ける。 FROM ruby:2.6.8 AS builder RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs RUN mkdir /app WORKDIR /app COPY Gemfile /app/Gemfile COPY Gemfile.lock /app/Gemfile.lock RUN gem install bundler:2.2.16 RUN bundle install FROM ruby:2.6.8 AS runner RUN apt-get update -qq && apt-get install -y libpq-dev nodejs RUN mkdir /app WORKDIR /app COPY --from=builder /usr/local/bundle /usr/local/bundle COPY . /app COPY dockerfiles/rails/entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 CMD ["rails", "server", "-b", "0.0.0.0"] 何もしていない状態から 20MB 程度減らすことができた。 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE vocabook_api_app latest 376df67aec60 35 minutes ago 1.09GB history コマンドでレイヤのサイズを見てみると、確かに 20MB くらい削減されている。 上:何もしないとき | 下:マルチステージビルド <missing> 4 hours ago RUN /bin/sh -c bundle install # buildkit 103MB buildkit.dockerfile.v0 <missing> 11 minutes ago COPY /usr/local/bundle /usr/local/bundle # b… 86.4MB buildkit.dockerfile.v0 マルチステージビルドは、コンパイルしてバイナリを生成するようなケースだと分かりやすく効果が出そう。 全部盛り FROM ruby:2.6.8-alpine3.14 AS builder RUN apk update -qq && \ apk add --no-cache build-base postgresql-dev nodejs && \ mkdir /app WORKDIR /app COPY Gemfile Gemfile.lock /app/ RUN gem install bundler:2.2.16 && bundle install FROM ruby:2.6.8-alpine3.14 AS runner RUN apk update -qq && \ apk add --no-cache postgresql-dev nodejs && \ mkdir /app WORKDIR /app COPY --from=builder /usr/local/bundle /usr/local/bundle COPY . /app COPY dockerfiles/rails/entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 CMD ["rails", "server", "-b", "0.0.0.0"] めっちゃ小さくなった。何もしない状態の半分近くまで小さくなっている。 $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE vocabook_api_app latest 4aebe416674b 2 minutes ago 588MB history を見てみると、apk addのところで差分が出ていそうなので、build-base を最終イメージに持ってこなかったのが良かったみたい。 <missing> 6 minutes ago RUN /bin/sh -c apk update -qq && apk add… 354MB buildkit.dockerfile.v0 <missing> 6 hours ago RUN /bin/sh -c apk update -qq && apk add --n… 547MB buildkit.dockerfile.v0 参考 いつも助かります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

routes.rbにnamespaceを設定した際のコントローラーの対応

Railsのroutesに以下のとおりnamespaceを追加設定した際に、それまで動いていたcontrollerが動かなくなってしまった。なお、コントローラのファイルはapp/controllers/api配下に移動済み。 routes.rb(変更前) Rails.application.routes.draw do resources :posts end routes.rb(変更後) Rails.application.routes.draw do namespace :api do resources :posts end end 事象 namespace設定までは、例えば以下のURLへのGETに対し合致するデータをJSONで返していた。 $ curl localhost:5000/posts/1 namespace設定後はURLが変わるだけかと思っていたが、エラーを返すようになってしまった。 $ curl localhost:5000/api/posts/1 ~ TypeError (superclass mismatch for class PostsController): 解決策 コントローラのクラス名も、ルーティングのnamespaceに合わせて「Api::」と階層を明示する必要があった。 posts_controller.rb(変更前) class PostsController < ApplicationController def show @post = Post.find(params[:id]) render json: { data: @post } end end posts_controller.rb(変更後) class Api::PostsController < ApplicationController def show @post = Post.find(params[:id]) render json: { data: @post } end end 補足 API::の代わりにmoduleで囲っても動いた。 posts_controller.rb module Api class SavingDetailsController < ApplicationController def show @saving_detail = SavingDetail.find(params[:id]) render json: { data: @saving_detail } end end end routesのnamespaceを先に書いてからコントローラをgenerateしていたら、最初から適切なクラス名にしてくれていたんだろうか?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails+Docker+MySQLのアプリをHerokuへデプロイする手順とエラーになったポイント

この記事では、Herokuへのデプロイ手順と私が引っかかったエラーを書き留めています。 説明不足、理解不足なところもあるかと思いますので、その際はご教授いただけると嬉しいです。 バージョン Rails 6.1.3 Ruby 2.6.8 MySQL 8.0 ストレージ AWS S3 Herokuへのデプロイでエラーになったポイント 最終的に以下の2つのポイントが原因でHerokuデプロイ時にエラーが出ていました。 bundlerバージョンが違う Herokuではデフォルトのbundlerバージョンが指定されています。 Gemfile.lockの末尾で確認できるBUNDLED WITHが 2.〜の場合はbundler2.0.2が、 1.〜の場合はbundler1.17.3が 指定がない場合はbundler1.17.3のバージョンが使われます。 私の場合、Ruby 2.6を使用していたため、bundlerバージョン1.17.2が入っており、これがHerokuデフォルトのbundlerバージョンである1.17.3より古いバージョンであることが原因で、デプロイ時エラーになっていたことが考えられます。 また、Ruby2.6からbundlerは組み込みになったため、bundlerの追加はできても、組み込みのbundlerバージョン自体は削除できません。 そのため、原則はRubyに組み込まれているbundlerのバージョンのままHerokuにデプロイするのが良さそうです。 環境変数の未設定 これはHerokuの仕組みをよく理解していないことから起こったイージーミスでした。。 環境変数の設定は下記で順番に説明していきます。 また、すでにHerokuにはログインしている前提で話を進めていきます。 config/database.yml を設定する config/database.yml production: <<: *default database: <%= ENV['APP_DATABASE'] %> username: <%= ENV['APP_DATABASE_USERNAME'] %> password: <%= ENV['APP_DATABASE_PASSWORD'] %> host: <%= ENV['APP_DATABASE_HOST'] %> まずこのように環境変数を設定します。 なお、環境変数は任意の名前で設定します。 私の場合は「APP_DATABASE〜」としています。 Herokuにアプリを作成 $ heroku create アプリ名 これでHerokuにアプリが登録されます。 このアプリ名は世界中で名前がカブらないように設定しないとエラーになります。 $ heroku create また、アプリ名を指定しなければ、Herokuがカブらないアプリ名を自動で作成してくれます。 Herokuにデータベースを作成 $ heroku addons:create jawsdb:kitefin 私は開発環境でMySQL8.0を使用しており、本番環境でもバージョンを統一させるためにjawsDBを選択しました。 (ちなみにclearDBがMySQL5.7まで対応のためです) 今回は説明用のため無料プランであるkitefinを入力しています。 $ heroku addons:create jawsdb:kitefin -a herokuのアプリ名 --version=8.0 またこのように入力すればバージョンの指定もできます。 現在のjawsDB MySQLデフォルトのデータベースバージョンは8.0となっているため、コマンドで指定しなくても8.0がcreateされます。 Herokuに各環境変数をセットする 環境変数の設定作業はHeroku上からでも設定できますが、今回はコマンド入力で進めていきます。 Heroku上で設定する場合、Herokuのアプリを開き、Settingsの中の「Config Vars」というところから環境変数をAddできます。 データベースURL $ heroku config:get CLEARDB_DATABASE_URL まずはこちらのコマンドを入力し、DB接続情報を確認します。 表示された情報を元に下記コマンドでMySQL2のURLをセットします。 $ heroku config:set DATABASE_URL='mysql2://ユーザー名:パスワード@ホスト名/データベース名?reconnect=true' ユーザー名 続いて先程のユーザー名の部分をコピーして次のように変数をセットします。 $ heroku config:add APP_DATABASE='ユーザー名' -a アプリ名 パスワード $ heroku config:add APP_DATABASE_PASSWORD='パスワード' -a アプリ名 パスワードも同じように入力します。 ホスト名 $ heroku config:add APP_DATABASE_HOST='ホスト名' -a アプリ名 ホストも同じように入力します。 データベース名 $ heroku config:add APP_DATABASE_HOST='データベース名' -a アプリ名 続いてデータベース名も入力。 S3アクセスキーID AWS S3をストレージとして使用している場合も同じように環境変数を設定していきます。 $ heroku config:add AWS_ACCESS_KEY_ID='アクセスキーID' -a アプリ名 S3シークレットアクセスキー $ heroku config:add AWS_SECRET_ACCESS_KEY='アクセスキーID' -a アプリ名 もし、Google mapなどのAPIをアプリケーションで使っているなら、そちらのAPIキーも同じように入力していけばOKです。 Herokuにアプリをデプロイする 環境変数の設定ができたら、いよいよデプロイに入ります! $ git add . ステージング環境にaddします。 $ git commit -m "heroku first commit" ローカルリポジトリにコミットして $ git remote add heroku Herokuのgit URL Herokuと繋ぎます。 git URLはHerokuのアプリページの中のSettingsで確認することができます。 (コマンドを入力したあと、error: remote heroku already exists.と出たらすでにこの作業は完了しています。) https://git.heroku.com/アプリ名.git URLはこのようになっています。 $ git push heroku master あとはHerokuにpushすればアプリのデプロイは完了です。 ブランチを切って作業している場合は $ git push heroku ブランチ名:master こちらのコマンドでpushします。 最終的には環境変数の設定は以下のようになりました。 JAWSDB_URL以下の環境変数はデプロイ時に設定されたものです。 最後にrails db:migrate 上記の設定でデータベースの作成までが完了しているため、最後に $ heroku run rails db:migrate こちらのコマンドを入力すれば、デプロイしたアプリケーションが正常に起動するはずです! もし、エラーが出たときは、その都度 $ heroku logs --tail こちらのコマンドでlogを確認してエラーの原因を探ります。 また、エラーを確認するために便利なaddons(papertrailなど)も用意されていますので、ご興味の有る方は調べてみてください。 この記事が、Herokuデプロイで困っている方の助けになると嬉しいです。 また誤っている箇所があれば、ご指摘いただけると非常に助かります。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【第10章】Railsチュートリアルでbcryptとfixtureについて忘れていたので復習した

Railsチュートリアル第10章(第6版)でhelper関連の演習が解けなかったので復習した。 ①ヘルパー ・Railsのビューでは膨大な数の組み込み関数が使える。 ・新しく作ることもでき、新しく作成したメソッドのことをカスタムヘルパーという。 ・Webサイトのレイアウトで使うコンパクトなヘルパーメソッドでは、メソッド定義、変数割り当て、論理評価、制御フロー、文字列の式展開 など、Rubyの様々な要素が投入されている。 ・デフォルトでは、ヘルパーファイルで定義されているメソッドは自動的にすべてのビューで利用できる。 ・ヘルパーメソッドはテストから呼び出せない。 ③まとめ ①で忘れていた箇所を思い出せた。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【個人開発】エンジニアは全員サーフィンやるべきだと思ったので、サーフィンしながらタイピングできるサービスを作ってみた。

はじめに エンジニアやエンジニア志望の皆さん、最後に海に行ったのはいつですか? 元々インドアで仕事や勉強をしがちで、コロナ禍もありよほど好きな人でなければ1年以上海に行ってないのではないでしょうか? 申し遅れました、りゅうじと申します。 【Twitter】 https://twitter.com/otokomigakimasu 【今回作ったサービス】 https://surf-typing.herokuapp.com/ サーフィンが好きでよく海に行く私は、普段インドアで仕事や学習を頑張っていらっしゃる皆さんこそ海に行くべきだと断言できます。もっと言えばサーフィンをやってみるべきです。 いつもPCと向き合っていて心身が受けているストレスは無自覚の内に蓄積されています。 その全てを海が洗い流してくれます。 しかし現実的に海が近い場所にないなら簡単には行けないし、サーフィンを趣味としていなければ真夏以外で海に行く事は少ないかと思います。 いつも通りPCを開いて海を感じられながら視覚的にサーフィン体験ができ、仕事や学習の効率化の為にタイピングも練習できて、心が癒されるものがあったらと思い…。 作りました。 https://surf-typing.herokuapp.com/ サービス概要 サーフムービーをバックに波の音とアコースティックギターの音色を聴きながらタイピング練習ができます。 問題の言葉は私がこれまでの人生で好きな言葉だったり、誰かに頂いた言葉だったり、自分の中で響いた言葉だったり…を出題しています。 人は普段見聞きしている言葉に影響を受けて生きています。 私はこれまでPCを購入してから既存のタイピングゲームを毎日欠かさず練習しておりました。 その中でタイピングゲームの問題の言葉が自身が想像しているよりも心に影響がある事を体感しています。 なので、問題を前向きな言葉だったり自身の思想を元にすれば、タイピングゲームからでも使っていただいた方を癒したり元気づけたり、私の人柄だったり、共有したい言葉を発信できると考えたからです。 使用技術 Ruby 2.6.6 Rails 6.0.3 JavaScript 主なGem meta-tags rubocop webpacker こだわったポイント すべての素材を自分で用意しました 使用した動画はGoProという手ブレ無しで撮影できるビデオカメラを購入して撮影してきました。 流れる波の音・アコースティックギターの音色・メロディも全て自分で作成し演奏しています。 エラー音はギターのハーモニクスを使いました エラー音が不快な音だとゲームをしていてストレスを感じます。 不快ではなく、きちんとエラー音として認識できる音を考えた結果、ギターのハーモニクスという綺麗な音が鳴らせる奏法のものを取り入れました。 気軽に使ってほしい想いからユーザー登録機能などを排除 いつものPC作業に入る前だったり、合間の時間に使ってほしいと思ったので、極力必要でないと感じた機能を排除する事を意識しました。 z派 j派 どちらにも対応 こちらは当初のリリース時は対応しておりませんでした。 私自身、対応させたかったのですが リリース前の開発段階では実装の方法を模索してもわからなく、必要最低限の機能だと感じなかったので、後回しにしMVPという事でリリースしていました。 しかし ji 'と'zi ' 'ju'と 'zyu' 'jo'と'zyo' をどちらにも対応させてほしいという要望を多数頂きアップデート致しました。 今後実装したい事 ステージを複数用意し選択できるようにする タイピングの成績の記録を残せるようにする 終わりに 最後まで読んでいただきありがとうございました。 色々書いてきましたが、私は海が…サーフィンが…ギターが好きだから、このアプリを作りました。問題の言葉は私の思想であり想いです。なのでこのアプリは私自身です。 私という人間を通して、皆さんが少しでも気持ちが明るくなったり、癒しになってもらえたり…。その中でタイピングが自然と速くなっていく手助けができたら嬉しいです。 それではまたSurfTypingでお会いしましょう! 【Twitter】 https://twitter.com/otokomigakimasu 【今回作成したサービス】 https://surf-typing.herokuapp.com/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

`require': cannot load such file -- rexml/document (LoadError)の対処法

メイラーの機能をつけるためrails g mailerを実行して その後の工程を進めていき rails s サーバー起動しようと思い実行すると なんだか果てしないエラー文がお出まし^^; ターミナルエラー文一部抜粋 #~省略~ `require':cannot load such file -- rexml/document (LoadError) #~省略~ 長文を読み解くと多分ここが原因かな...?と その後もいろいろrails コマンドを試しても 全てrailsコマンドは死亡(全部エラー吐く これでエラーが出るようになるという事は 毎回追加しないといけないgemっぽいのでメモ 参考サイト 記事 gemサイト Gemファイルに追記 gem 'rexml', '~> 3.2', '>= 3.2.5' 追記後 bundle install このgemを入れたら問題は解決しましたが、 なんか理由が明確にわからないから不安、、、 どなたか理由が分かる方いたらコメントで教えていただきたいです! 環境 rails6.1.4 ruby3.0.1
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[個人開発]Discordの使用時間を計測するアプリ 「Discord-Log」を作りました

はじめに この度、Discordのボイスチャンネルの使用時間を自動で計測するアプリ「Discord-Log」をリリースしました。 このアプリは、RUNTEQというプログラミングスクール内限定でリリースしているアプリになりますので、サービスリンクは載せていません。 ご了承ください。 Discord-Log このサービスを作った背景 結論からお伝えすると、私がDiscordのボイスチャンネルにいる時間=学習時間であったため、簡単にその時間を計測できるサービスが欲しいなと感じたからです。 私自身、学習時間を計測する習慣はありませんでしたが、学習するときはDiscordを使うようにしていました。 そのため、Discordのボイスチャンネルを使用した時間を計測できれば、学習時間がわかるよね。ってことで、このアプリを作りました。 ※ここからは少し長いので、興味がない方は次章へ進んでください。サービスを作った背景を語ります。 みなさんは、こんな言葉を聞いたことはありませんか? 「エンジニア転職に必要な学習時間 1000時間」 私のアプリの構想はこの言葉から始まりました。 その時、既にプログラミングを学習し始めて5ヶ月が過ぎていました。 その当時の私は学習時間を計測する習慣がなく、 「自分って今どれくらい学習をしてきてるんだろう。」 「5ヶ月過ぎてるってことはそろそろ1000時間に近づいてきているのかな?」 「てか、本当にプログラミングの力が、身についているのかな?」 「はあ、、、自分はエンジニアになれるのだろうか。。。」 なんてことを考えながら、自分の現在地がわからなかったり、自分のやってきたことに自信が持てなくなり、日々の学習に対するモチベーションが乱高下していました。 学習時間を計測することと、モチベーションには相関関係があります。 東大生が教える!時間を計って勉強すると圧倒的に勉強効率が上がる5つの理由 私自身もそのことを知って、タイマーやポモドーロテクニックのような学習法を試してみたのですが、タイマーを押し忘れたり、記録をするのが面倒になってやめたりと、そう長くは続きませんでした。 RUNTEQでは、コミュニティのコミュニケーションツールとしてDiscordを使用しています。 Discordでは、受講生や卒業生、運営チームと気軽にお話をすることができます。 また、自習室のようにみんなで集まって勉強したり、議論したり、先輩に相談してみたり、勉強の息抜きとして他の受講生と雑談したりなどなど、24時間365日好きなときに利用することができます。 毎日のようにたくさんの受講生や卒業生、運営チームが交流をしています! いいですよね!! 私も学習の場として、Discordを使用していました。 そんな時に思ったのが、「Discordの使用時間を自動で測ってくれるアプリがあれば、学習時間を記録せずに済むな」と、 そう思いました。 私自身やRUNTEQ受講生が日々の学習やコミュニケーションの場として使っているDiscordで、使用している時間を計測して視覚化することがができれば、「時間を測ること」と「時間を記録すること」の2つの作業を自動で行うことができます。これは正しく一石二鳥、一挙両全です!! よし!これだ!! こうして「Discord-Log」を作ってみようと決意しました! だらだらと、すいません。 お付き合いくださった方ありがとうございます。大好きです。 サービス概要 Discord-Logは、登録したDiscordアカウントのボイスチャンネルの使用時間を自動で計算して、グラフとして視覚化するアプリです。 このアプリは、Rubyで作成したDiscordのBotアプリと、Railsで作成した「Discord-Log」の合わせ技になっております。 ターゲットユーザー RUNTEQの受講生 望む未来 このアプリを通じて、1人でも多くの人がモチベーションの波を抑えて1000時間の学習到達目標に到達し、エンジニア転職を成功させること。 使い方 Discord-Log Discordのボイスチャンネルに入室して退出後、画面をリロードすると時間が計測され表示されます 1.アプリの登録の仕方 サーバーの管理者の場合  ①「サーバーにBotを登録する」 サーバーの管理者の方は、自身が管理者になっているサーバーにBotを登録してください。 このBotは、ボイスチャンネルの入室・退出の際にRailsアプリのエンドポイントに対してPOSTリクエストを送信しています。 (※サーバーにBotが登録されていないと使用時間を計測することはできません。) ② Discordアプリの「サーバー設定」 → 「ウィジェット」 → 「ウィジェットを有効化」をクリック このウィジェットを有効化することにより、サーバー内のチャンネル情報を取得することができるようになります。 サーバーの管理者の方とその他のユーザーの共通処理 ① アプリの「Discord-Logを使う」より、「認証」を行ってください。「認証」が済みますと、アプリに登録したことになります。 認証からマイページ画面に行く間に、認証処理でユーザーを登録し、DiscordAPIの「ユーザーが所属している全てのサーバー」と「それらサーバーの全てのボイスチャンネル」を取得するエンドポイントに対してGETリクエストを送信しています。 そして、遷移後にはそれらの情報が表示されるようになっています。 2.機能の紹介 ①マイページ ログイン後に遷移する画面になります。 この画面では、ログインユーザーの全サーバーのボイスチャンネルの使用時間を確認することができます。 サイドバーには、ログインユーザーが所属している全てのサーバーが表示されます。 その下には、そのサーバー内にある、ボイスチャンネルが表示されるようになっています。 (この時、登録されているサーバーやチャンネルが表示されていない時は、「アプリの登録の仕方」の「ウィジェットを有効化」する処理ができてない可能性がありますので、ご確認ください。) ②サーバー詳細ページ サイドバーにある、サーバー名のボタンリンクをクリックすることで、サーバーの詳細ページへ遷移することができます。 この画面では、そのサーバー内にある、ボイスチャンネルの使用時間を確認することができます。 ③ボイスチャンネル詳細ページ 同じく、サイドバーにある、ボイスチャンネル名のボタンリンクをクリックすることで、ボイスチャンネルの詳細ページへ遷移することができます。 この画面では、そのボイスチャンネルの使用時間を確認することができます。 使用技術 ○Railsアプリ(Discord-Log) Ruby 2.6.6 Ruby on Rails 6.0.3 Discord API  slim sorcery chartkick Bootstrap config dotenv-rails ○Rubyアプリ(Botアプリ) Ruby 2.6.6 discordrb net/http uri json 開発期間 約1.5ヶ月 ER図 工夫したこと 1.アプリの継続利用における障壁を無くすこと コンセプトとしては、「めんどくさがり屋な自分でも使いたいと思えるか?」ということを念頭に置いて、自問自答しながら作成しました。 このアプリに、一度登録すると、自動的にBotとRailsアプリのDBに時間が保存・計測されます。 なので、ユーザーは一度登録をして、Discordの使用時間を振り返りたいと思ったタイミングで再度アプリに訪問するだけで、それまでの使用時間が表示されます。 これによって、毎日の学習時間を自分で記録する手間が省けます。 これならめんどくさがり屋な私でも、使いやすいですよね! 2.Discordを普段と同じように使って、時間が計測されるようにすること DiscordのBotは、サーバー内のユーザーの音声ステータスが変更された時に動いています。 (音声ステータスとは、DiscordのカメラのON・OFFやミュートのON・OFF、スピーカーのON・OFFなどです。) このユーザーの音声ステータスが変わったタイミングで、そのユーザーがボイスチャンネルにいるかどうかBotが確認しています。 ボイスチャンネルにユーザーが居ればどのユーザーが、どのチャンネルに入室したかの入室処理を、ユーザーが居なければどのユーザーが、どのチャンネルを退出したかをRailsアプリにPOSTリクエストで送信しています。 チャンネルに入室・退出した際は必ずこれらのアクションが行われるため、このBotのイベントが発火します。 しかし、以下のように2つの問題が起きました。 ①音声ステータスが変わるごとに入室リクエストが送られてしまう問題 ②ボイスチャンネルの横移動をしたら前居たボイスチャンネルが退出処理されていない問題 これらを解決しない限り、普段通りにDiscordを使いながら簡単に学習時間の計測ができません。 なのでめんどくさがり屋な私は使いたくないなと思いました。 どのように解決したのかは、長くなるので割愛しますが、これらを解決することで、Discordを普段通りに使用しても時間が計測されるようになりました。 3.サーバーやボイスチャンネル名の名前の変更や削除に対応すること Discordアプリで、サーバーやボイスチャンネルの名前を変更したり、削除したりすることはよくあると思います。 変更の場合は、DBにあるデータをログイン時のAPIを叩いたタイミングで更新するようにしました。 削除の場合は、ログイン時のAPIを叩いたタイミングでDBにあるサーバー(もしくはチャンネル)のIDだけを配列をまず取得します。それらをAPIを叩いて取得した情報と照らし合わせ、取得してきた情報があれば配列から削除します。最後に配列から残ったIDをDBから削除するようにしました。 終わりに 初めての投稿で、拙い文章で申し訳ないです。 それでも、最後まで読んでくださりありがとうございました。 もし大人数のサーバーで勉強するのが中々難しいよって人もいると思います。 その時は、個人でサーバーを作って、使用することもできます。(ちなみに僕もそうやって最初は使っていました。) 僕のように自分の現在地がわからずにモチベーションを下げてしまうことなく、学習を続けてくれる受講生が1人でも増えればいいなって思っています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ruby on rails Windows10 64ビットインストール Railsサーバー起動までlocalhost:3000

ruby on railsの設定方法について色々ぐぐったりしたけどほとんど間違っている内容だったので、正しいやり方を書きます。 rubyのダウンロード https://rubyinstaller.org/downloads/ インストール# 「Ruby+Devkit 2.7.4-1 (x64)」 です。 最新版をインストールするのは辞めました。 最新版が良いという方は最新版でやってみると良いかもしれません。 WITH DEVKITを選ぶ理由はツールがまとめられてるからです。 自分が使ってるWindows10が32ビットか64ビット数を間違えないように注意します。 自分が使っているWindowsのビット数の確認方法は Windowsのシステムからビット数を確認できます。 確認方法 コントロール パネル\すべてのコントロール パネル項目\システム Windowsの左下の検索バーで「コントロールパネル」を入力 Windows10 64bit=(x64) Windows10 32bit=(x86) インストールまでの作業 MSYS2にチェックを入れて次へ進む ※インストール後にこの記事を作成しており、すでにインストールされているというATTENTIONが表示されています。 ※MSYS2とは 公式日本語訳 MSYS2 MSYS2は、ネイティブWindowsソフトウェアを構築、インストール、および実行するための使いやすい環境を提供するツールとライブラリのコレクションです。これは、mintty、bashと呼ばれるコマンドライン端末 、gitやsubversionなどのバージョン管理システム、tarやawkなどのツール、さらにはautotoolsなどのビルドシステムで構成され、すべてCygwinの修正バージョンに基づいています。これらの中心的な部分のいくつかはCygwinに基づいていますが、MSYS2の主な焦点は、ネイティブWindowsソフトウェアのビルド環境を提供することであり、Cygwinを使用する部分は最小限に抑えられています。MSYS2は、ほんの数例を挙げると、GCC、mingw-w64、CPython、CMake、Meson、OpenSSL、FFmpeg、Rust、Rubyの最新のネイティブビルドを提供します。パッケージの簡単なインストールとそれらを最新の状態に保つ方法を提供するために、ArchLinuxユーザーにはなじみのあるPacmanと呼ばれるパッケージ管理システムを備えてい ます。依存関係の解決や単純な完全なシステムアップグレードなどの多くの強力な機能に加えて、簡単で再現性のあるパッケージ構築をもたらします。私たちのパッケージリポジトリには、インストールの準備ができている2000以上のビルド済みパッケージが含まれています。https://www.msys2.org/ コマンドプロント MSYS2のインストール RUbyinstaller 1-base installation 2-system update(optional) 3-and MINGW development toolchain ここで[1,2,3]の選択がありますが、 1,2,3とまとめて入力してENTERを押します。 その後、10分ほどインストールに時間を要します。 完了した後、また 1-base installation 2-system update(optional) 3-and MINGW development toolchain と出ますが、無視してENTERを押すとコマンドプロントが閉じされます。 これでRubyやMSYS2のインストールは終わりです。 Rubyがインストールされているのかruby -vで確認する コマンドプロントを起動させます。Windowsの左下の検索ボックスから「cmd」を入れてコマンドプロントを呼び出します。 Ruby -v と入力してENTER Railsのインストール コマンドプロントを起動 cmd gem install rails ※rubyに何かをインストールするときはgem installと最初に入れればOK しばらく待つとインストールが完了します。 railsがインストールされているのかrails -vで確認 インストール完了後に-vを入れて確認 rails -v SQLite3をインストールする SQLite3のライブラリをインストールします。 コマンドプロントを起動 cmd gem install sqlite3 しばらく待つとインストールが完了します。 SQLite3のEXEファイルやDLLなどを用意 ここでダウンロードします。 https://www.sqlite.org/download.html ダウンロードページに移動後、少し下へスクロールすると、 Precompiled Binaries for Windowsという項目があります、。 それがWindows用のファイルです。 私はWindows 64ビットだったので(sqlite-dll-win64-x64-3360000.zip)を選びました。 32ビットの人は上(sqlite-dll-win32-x86-3360000.zip)を選んでください。 次にexeファイル(実行ファイル)である sqlite-tools-win32-x86-3360000.zipをダウンロードします。 ※なぜ実行ファイルは32ビットしかないのか不明ですがこれを使います。 ダウンロードしたフォルダを解凍 ZIPファイルなので通常はダブルクリックで中身を取り出せると思います。 sqlite3.dllやexeファイルをrubyのbinフォルダへ移動 まずはsqlite3.dllだけを選びます。 exeファイル 中に三つあるので全部binフォルダへ移動させます。 sqlite3.exeだけ入れるといったことが書かれてているサイトなどがあり、その通りに何回やっても失敗しましたが、全部入れたらうまくできました。 移動先 Rubyのフォルダを開きbinへ rubyは通常Cドライブにあります。 一番上にbinフォルダがあるので、ここへさっきのdllファイルを入れます。 Rubyのbinフォルダへ移す 合計4つ移動させます。 Node.jsをインストール Node.jsの公式サイトでファイルをダウンロードします。 https://nodejs.org/ja/ 左の推奨版をダウンロード すべてNEXTを押してインストールを進めます。 ※途中、チェックボックスがありますが、チェック入れました。 Windowsのシステムの環境変数でNode.jsの実行ファイルのパスを通す 左下のWindowsのアイコン上で右クリックしてシステムを選択します。 少し下へスクロールすると関連設定にシステム情報があるのでそれをクリックします。 次に「システムの詳細設定」があるのでそれをクリック 次にシステムプロパティの詳細設定タブが表示されているので、一番下にある「環境変数」をクリック。 ユーザーの環境変数のところにPathがあるのでそれをダブルクリック ※以前にrubyの最新を入れて失敗したのでその時のパスが残ってます。 Node.jsの実行ファイルパスを入力します。 参照をクリックして、C:\Program Files\nodejs\node_modules\npm\bin\node-gyp-bin Program Filesは Program FilesとProgram Files(x86)があります。node.jsは前者にあります。 これの設定にあたってはこちらのURLを参考にしました。 https://note.affi-sapo-sv.com/nodejs-windows-setting-knowledge.php 設定後は「OK」を押して環境変数のウインドウを閉じます。 node.jsのバージョンを確認 node.js -v バージョンが表示されたらインストール完了です。 yarnをインストールします(作業はnode.jsと同じ) yarn公式ページ Windowsインストーラーダウンロードページ https://classic.yarnpkg.com/en/docs/install#windows-stable 公式ページに移動後少し下へスクロールします。 コマンド入力でなく、 「Alternatives」 Click to expand / collapse でクリックします。 OSは「Windows」バージョンは「classic stabke」 インストールはすべてNEXTを選べばよいと思います。 環境変数でyarnのpathを通します。 参照から、C:\Program Files (x86)\Yarn\bin でOKです。こちらのプログラムファイルはx86の方を選びました。 yarnがインストールされているか確認 yarn-v  Rails アプリを作成 rails new アプリケーション名 かなり長いですが しばらく待つと完了します。 Railsを起動させる rails s これで表示されます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[テスト]FactoryBotにimageを記述する

はじめに 本記事は、テストコードに関するもので、 FactoryBotにimageを記述する方法(factoriesディレクトリ)を記述します。 本日、何時間も悩んだものになるので、 短いですが、アウトプットしたいと思います。 単体テストコードに関する記事は、以下も参照してください。 内容的には、ターミナルにて以下を記述後のこととなっております。 % rails g rspec:model テーブル名 FactoryBotにActiveStorageの記述をする 例 早速ですが、例文です。 FactoryBot.define do factory :tweet do content {Faker::Lorem.sentence} association :user after(:build) do |tweet| tweet.image.attach(io: File.open('ディレクトリ名/画像ファイル名'), filename: 'ファイル名') end end end afterメソッド 任意の処理の後(ここでは、(:build))の後に、 指定の処理((:build)以降の処理)を実行することができます。 io: File.open ここで設定したパスのファイル('ディレクトリ名/画像ファイル名')をfilenameを'ファイル名'として実行しています。 association 今回の趣旨とは、やや離れますが、 対象のテーブルとのアソシエーションも忘れずに記述しましょう。 association :user 実際にしてしまったミス ①テーブルのカラムに存在すると勘違いしてしまい、 今回の例文に沿って記述すると // (注意)謝った記述です。 FactoryBot.define do factory :tweet do         image {'ディレクトリ名/ファイル名'} ←間違い!! content {Faker::Lorem.sentence} association :user end end と記述してしまった。 ②カラムに存在しないことがわかり、アソシエーションするのかと勘違いした。 // (注意)謝った記述です。 FactoryBot.define do factory :tweet do content {Faker::Lorem.sentence} association :user         association :image                 ←間違い!! end end 終わりに 学習したことでしたが、全く初見のように思えてしまいました。 このようなことは今後も増えてきてしまうかと思いました。 しかし、忘れているのなら、 次は、理解して覚えていたら良いことです。 後ろは、フィードバックする時以外は向かず、 新しく出てきたものや、再度登場してきたものをどんどん理解していきたいと思います。 それでは、 明日も頑張りましょう!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SQLを学ぼう!【3章】

前章の記事(SQLを学ぼう!【2章】)では、MySQLに接続し、挿入したデータを検索して表示させる作業を行なっていきました。今回は挿入したデータを更新する方法とデータを削除する方法について行なっていきたいと思います! 前章をご覧になりたい方は下記のリンクからご参照ください。 ・SQLを学ぼう!【1章】 ・SQLを学ぼう!【2章】 データを更新する 前章(SQLを学ぼう!【2章】)で作成したデータを今一度、確認しておきます。 ターミナル mysql> select * from list; +------+---------------+---------------------------+-------+ | id | name | address                                | age | +------+---------------+---------------------------+-------+ | 1 | 学 太郎               | 東京都世田谷区            |23 | | 2 | 中村 二郎  | 愛知県稲沢市                             |30 | | 3 | 山科 三郎            | 千葉県千葉市               |27 | | 4 | 鈴木 一郎            | 福岡県飯塚市               |35 | | 5 | 中山 五郎            | 山梨県山梨市                             |20 | | 6 | 小林 大介            | 東京都千代田区                         |40 | | 7 | 大場 芳樹            | 東京都港区                               |26 | | 8 | 中川 博之            | 滋賀県彦根市                             |31 | | 9 | 宮崎 遥輝            | 東京都八王子市                          |29 | | 10 | 青木 正太郎       | 東京都町田市                             |47 | | 11 | 佐藤 美穂          | 福島県白河市                            |24 | +------+---------------+---------------------------+--------+ 11 rows in set (0.17 sec) このすでに挿入したデータを更新していきたいと思います。 更新するには、UPDATE文を用います。 UPDATE テーブル名 SET カラム名1 = 値1, カラム名2 = 値2, ..., カラム名n = 値n WHERE 条件文; 今回は、listテーブル内の「id = 5」のレコードに更新をしていきます。年齢を「30」に変更してみます。 ターミナル mysql> UPDATE list SET age = 30 WHERE id = 5; Query OK, 1 row affected (0.17 sec) Rows matched: 1 Changed: 1 Warnings: 0 データの変更が更新されているか、確認してみます。 ターミナル mysql> SELECT * FROM list WHERE id = 5; +------+---------------+---------------------------+------------+ | id | name | address | age | +------+---------------+---------------------------+------------+ | 5 | 中山 五郎   | 山梨県山梨市 | 30 | +------+---------------+---------------------------+------------+ 1 row in set (0.17 sec) 変更が確認できました! 最後に挿入したデータの削除方法についてです。 データを削除する データを削除するには、DELETE文を用います。 DELETE FROM テーブル名 WHERE 条件文; ここでは、「idが6より大きい人のレコード」を削除してみます。 ターミナル mysql> DELETE FROM list WHERE id > 6; Query OK, 5 rows affected (0.19 sec) データが削除されたかどうか、全件を表示して確認します。 ターミナル mysql> select * from list; +------+---------------+---------------------------+-------+ | id | name | address                                | age | +------+---------------+---------------------------+-------+ | 1 | 学 太郎               | 東京都世田谷区            |23 | | 2 | 中村 二郎  | 愛知県稲沢市                             |30 | | 3 | 山科 三郎            | 千葉県千葉市               |27 | | 4 | 鈴木 一郎            | 福岡県飯塚市               |35 | | 5 | 中山 五郎            | 山梨県山梨市                             |20 | | 6 | 小林 大介            | 東京都千代田区                         |40 | +------+---------------+---------------------------+--------+ 6 rows in set (0.17 sec) 削除されていますね! これで、データの更新と削除が完了しました! 最後までご覧いただきまして、ありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む