- 投稿日:2021-03-05T22:28:14+09:00
【Rails】ルーティングを名前空間でグループ分けしている場合のform_withの記法【Ruby】
はじめに
ヘルパーメソッドのform_withを使用して入力フォームを作ろうとしていたところ、NoMethodErrorが出てしまい詰まっていました。
結論
route.rb
においてルーティングを名前空間shops
でグループ分けしていたにもかかわらず、shops
をform_withの引数に渡していなかった。コード
route.rbnamespace :shops do resources :posts ---中略--- endshops/posts_controller.rbclass Shops::PostsController < ApplicationController --- 中略 --- def new @post = Post.new end --- 中略 --- endshops/post/new.html.erb<%= form_with model:[:shops,@post], local: true do |f| %> --- 中略 --- <div> <%= f.submit "送信"%> </div> <% end %>resourcesをネストしているときに親モデルを引数に渡すのと同じ感覚です。
参考
https://techracho.bpsinc.jp/ohno/2019_12_10/84349
https://railsdoc.com/page/form_with
- 投稿日:2021-03-05T20:47:57+09:00
【Rails】私が成長を実感したエラーの対処法
はじめに
本記事では、私がRailsで学習をする際に、エラーが出た場合の向き合い方について記載しております。
「エラーが出たときが成長する一番のチャンス」という考えの元、エラーとしっかり向き合う姿勢を持って取り組んできたので、その方法を共有します。前提
以下のgemをインストールします。
既に導入済みの方は読み飛ばしてください?♂️導入するgem一覧① better_errors ② binding_of_caller ③ pry-byebug① better_errorsとは
Better Errors replaces the standard Rails error page with a much better and more useful error page. It is also usable outside of Rails in any Rack app as Rack middleware.
(better_errorsのGitHub公式から引用)要するに、標準のRailsのエラーページをより見やすく、より便利なエラーページに置き換えてくれるgemです。
Using binding_of_caller we can grab bindings from higher up the call stack and evaluate code in that context. Allows access to bindings arbitrarily far up the call stack, not limited to just the immediate caller.
(binding_of_callerのGitHub公式から引用)要するに、binding_of_callerを使うと、irbを使用して、エラー画面上でコードを評価することができるようになるということです。
③ pry-byebugとは
Adds step-by-step debugging and stack navigation capabilities to pry using byebug.
(pry-byebugのGitHub公式から引用)要するに、pryを使用してデバッグするためのデバッグツールです。
irbのように対話的にrubyを実行できるようになります。To use, invoke pry normally. No need to start your script or app differently. Execution will stop in the first statement after your binding.pry.
(pry-byebugのGitHub公式から引用)ブレークポイントをうちたい場所に
binding.pry
を書いて処理を実行すればその箇所で処理が止まり、変数の値などを確認できるようになります。では3つのgemをインストールしましょう
Gemfilegem 'better_errors' gem 'binding_of_caller' group :development, :test do gem 'pry-byebug' endターミナルで
$bundle install
を行えばgemのインストールは完了です。エラーの解決方法
① エラーログを確認する
エラー画面に表示される「エラー文」「トレース情報」「irb」をもとに『エラーの本質』と『エラーの場所』を特定します。
② デバッグ
エラーの原因と場所が特定できたら、デバッグを行い「どのようなことが」「どんな風に起こったのか」をさらに詳しく具現化します。
エラー画面のirb上でも変数の値などを確認できます!
③ 情報収集
エラーログとデバッグで得た情報をもとに、公式ドキュメントやGitHubの公式ページを見て問題に対応する情報を集めます。
④ 収集した情報をもとに仮説を立てる
集めた情報を自分で組み立てて仮説を立てます。
→この過程が一番頭を使うので成長していると実感できます。⑤ 仮説をもとに検証する
仮説をもとに実際に検証します。
解決できない場合は再度仮説を立てて検証します。⑥ 解決できるまで②〜⑤を繰り返す
- 投稿日:2021-03-05T20:39:17+09:00
Railsでおすすめ機能を作る
質問掲示板風サイトを作っている時、おすすめ機能を実装したいと思ったので作りました。
前提
・既読機能がある
・タグ機能がある(acts-as-taggable-on使用)
・devise使用流れ
①自分が最近見た記事から30件取得
②記事に紐づいているタグを取得し、さらにその中から数が多い順に5件取得
③5件の中からランダムに1個のタグを取得
④取得したタグを含み、かつユーザー自身の投稿ではない投稿を取得
⑤他のユーザーからの既読数が多い投稿順に並べ替えて表示実際の記述
参考程度にお願いします。(元はコントローラーに書いていたのですが、リファクタリングの際にService層を作りそこに移行したため)
app/controllers/top_pages_controller.rb#Service層とやりとりするための記述です。実際の流れは次にあるのでそちらを参考にするといいと思います。 def index if user_signed_in? @recommend_tag = Posts::Postservices.recommend_tag(current_user) @recommend_posts = Posts::Postservices.recommend_posts(current_user, @recommend_tag, params).includes(:user, :taggings) end endapp/services/posts/postservices.rbmodule Posts module Postservices def self.recommend_tag(current_user) #①自分が最近見た記事から30件取得 recommends = current_user.already_reads.order("created_at DESC").limit(RECENTLY_READ_POST_COUNT) #②記事に紐づいているタグを取得し... array = Array.new recommends.each do |reco| reco = Post.find_by(id: reco.post_id) reco = reco.tag_counts_on(:tags).pluck(:name) #(Postモデルからtagsカラムのタグ名を取得。tag_counts_onはacts-as-taggable-onのメソッド。) array.push(reco) end #②...数が多いものから順に5件取得&③5件の中からランダムに1個のタグを取得 return array.compact .flatten .group_by{|e| e} .sort_by{|_,v|-v.size} .map(&:first)[0..4] .sample end def self.recommend_posts(user_id, recommend_tag, params) #④取得したタグを含み、かつユーザー自身の投稿ではない投稿を取得 posts = Post.tagged_with(recommend_tag).where.not(user_id: user_id) #⑤他のユーザーからの既読数が多い順に並べ替えて表示 return posts.select('posts.*', 'count(already_reads.id) AS already_reads') .left_joins(:already_reads) .group('posts.id') .order('already_reads desc') .order('created_at desc') .limit(DEFAULT_PAGE_ITEM_COUNT) .page(params[:page]).per(DEFAULT_PAGE_ITEM_COUNT) end end endconfig/initializers/constants.rb#あとで修正しやすいように件数などは変数で渡しています DEFAULT_PAGE_ITEM_COUNT = 10 RECENTLY_READ_POST_COUNT = 30app/views/top_pages/index.html.rb<!-- viewの参考程度においときます... --> <div class="main-contents"> <% if user_signed_in? && @recommend_posts.present?%> <h3 class="title-index"><%=@recommend_tag%>に興味がある人におすすめ</h3> <% @recommend_posts.each do |reco| %> <div class="post-index"> <div class="overall-container"> <%= link_to post_path(reco.id) do %> <div class="title"><%= reco.title %></div> <% end %> <div class="tag-container"> <% reco.tag_list.each do |tag| %> <span class="post-tag"><%= link_to tag, root_path(tag_name: tag) %></span> <% end %> </div> <div class="other-container"> <% if user_signed_in? %> <div class="already-read"><%= render 'shared/alreadyread', post: reco %></div> <% end %> <div class="comments-count"><i class="far fa-comment"></i> <%= reco.comments.count %></div> <div class="likes-count"><i class="far fa-heart"></i> <%= reco.likes.count %></div> <div class="user-info"> <% if reco.user.image? %> <div><%= image_tag post.reco.image.url, class: "user-index-icon" %></div> <% else %> <div><%= image_tag "/assets/default.jpg", class: "user-index-icon" %></div> <% end %> <div class="user-name"> <%= reco.user.name %></div> </div> </div> </div> </div> <% end %> <div class="paginate"><%= paginate @recommend_posts %></div> <% end %> </div>↓こんな感じで表示できます(CSSはお好みで調整してください)
終わりに
自分で作ったおすすめ機能を紹介しました。拙い機能ではありますが、自分で考えて色々調べて実装した初めての機能なのでとても思い入れがあります。
ほぼコードを載せただけになってしまいましたが誰かの参考になれば幸いです。
- 投稿日:2021-03-05T19:39:13+09:00
【Rails】Bootstrap 4 Tag Input Plugin With jQueryを使ってタグの見た目をよくする
タグ入力後の見た目をよくしよう
題名通りBootstrap 4 Tag Input Plugin With jQueryを用いてタグ入力後の見た目をよくしていきます。
(2回目の)初投稿なので誤字や脱字など至らない点はあると思いますが、よろしくお願いいたします。完成形
現在のタグ入力機能は下のような感じですが、このままだと殺風景なので
↓のように入力後にきれいに装飾されるようにしてみましょう。
またエンターキーでもタグの登録ができるようになります
前提
Rails 6.0.3.5
Bootstrap 4.5
jQuery導入済み
タグ機能を実装済みBootstrap 4 Tag Input Plugin With jQueryの導入
Bootstrap 4 Tag Input Plugin With jQueryの公式サイト
上の公式ページからダウンロードしてきましょう。
解凍すると
Bootstrap-4-Tag-Input-Plugin-jQueryというディレクトリができます。
ディレクトリ内の
tagsinput.js
を
app/javascripts
に
tagsinput.css
を
tagsinput.scss
に名前を変えて
app/javascripts/stylesheets
にいれましょう。
tagsinput.jsを読み込めるように記述します。
application.jsrequire('jquery') require('tagsinput') //←これを追加しますtagsinput.scssを読み込むための記述もします。
application.scss@import '~bootstrap/scss/bootstrap'; @import '~@fortawesome/fontawesome-free/scss/fontawesome'; @import 'tagsinput'; //←これを追加します。導入はこれで終わりです。
データをdata-roleで送ってtagsinputを起動させよう。
タグの入力フォームにdata-roleを追加します。
new.html.erb<div class="tags"> <%= f.label :tag_list %> <%= text_field_tag 'post[tag_list]', @post.tag_list, data: {role: "tagsinput"} %> # ←を追加 </div>少し設定を変えてみましょう
現状だとタグが無限に登録できてしまします。
追加できるタグの数と最大文字数を設定しましょう。
tagsinput.jsの中身を少しみてみましょう。tagsinput.jsmaxTags: undefined, maxChars: undefined,こちらを見るとタグの文字数の縛りもないことがわかりますね!!!
こちらを変えていきましょう。tagsinput.jsmaxTags: 4, maxChars: 8,タグの最大数4と最大文字数8と設定できました。
↑半角英数ならいいのですが、全角入力時に入力画面にずれがでてしまいます。
こちらを直していきましょう。tagsinput.jsthis.inputSize = Math.max(1, this.placeholderText.length);こちらを
tagsinput.jsthis.inputSize = Math.max(20, this.placeholderText.length);いい感じになりましたね!!!
最後に
導入にするにあたっていろいろなエラーと戦いましたorz
bootstrap-tagsinput最初はこちらを導入しようとしたのですが、古いためかなかなかうまく行かずorz
またTag itの導入もうまくいかずorz
いろいろ探し求めてこちらにたどり着きました。またBootstrap 4 Tag Input Plugin With jQueryについての日本語の文献がなく、オプションなどについて英語の公式を参考にしました。
英語だからといって苦手意識を持たない。しっかりと読むことの大切さを知りました(遅い)
ついでにgifのマークダウン記法があるのを初めて知りました(遅い)
以上となります。拙い文章ですがご拝読いただきありがとうございました。
- 投稿日:2021-03-05T17:32:32+09:00
Wikipediaの情報をCSVに保存し、Railsで活用
野球選手のデータをRailsで扱いたかったんですが、活用できるAPIがなかったので自力で集めてみました。
PythonはGoogleColaboratoryを使用してます。0.流れ
Wikipediaの情報をGoogleColaboratoryからスクレイピング
↓
データを整え、CSVに保存
↓
Railsのデータベースに保存
↓
表示する1.スクレイピング
準備
import requests import pandas as pd from bs4 import BeautifulSoup # 広島東洋カープの選手一覧 url = "https://ja.wikipedia.org/wiki/%E5%BA%83%E5%B3%B6%E6%9D%B1%E6%B4%8B%E3%82%AB%E3%83%BC%E3%83%97%E3%81%AE%E9%81%B8%E6%89%8B%E4%B8%80%E8%A6%A7"データの取得
df = pd.read_html(url) df[5].head()df[4]には投手の情報が入っています。
df[5],[6],[7]に捕手、内野手、外野手の情報が入っているので、野手として1つにまとめます。df_before_fielder = pd.concat([df[5], df[6], df[7]], ignore_index=True) #新たに行番号の割り当てのオプションカラム名を英語表記に変更
df_before_fielder.rename(columns={"背番号":"number", "選手名":"player_name", "投":"hand", "打":"bat", "備考":"remark"}, inplace=True) df_fielder=df_before_fielder.drop("remark", axis=1) #備考蘭がいらなかったため削除 df_fielder["team"]="C" #チーム名として、カープ(Carp)の省略英表記のCを追加 df_fielder.head()2.CSV保存
from google.colab import files import datetime # ファイル名に日時を挿入 dt_now = datetime.datetime.now() dt = dt_now.strftime('%Y%m%d%H%M%S') file_name = "c_pitcher_"+ dt + ".csv" # CSVに変換 df_fielder.to_csv(file_name, index=False) # CSVファイルのダウンロード files.download(file_name)3.Rails
モデルの作成
$ rails g model Player number:integer player_name:string hand:string bat:string team:stringCSVの情報をデータベースに保存
require 'csv' CSV.foreach("c_fielder_20210304124010.csv", headers: true) do |row| #「headers: true」先頭行をヘッダーとするので、rowには入らない Player.create!( # rowには、["22", "中村奨成", "右", "右", "C"]と各選手の情報が入っている。 number: row[0].to_i, #ファイル内で文字列になっていたため数値へ変換 player_name: row[1], hand: row[2], bat: row[3], team: row[4] ) end puts "Finish!"コントローラの作成
$ rails g controller Players indexコントローラからDBの情報を取得
class PlayersController < ApplicationController def index @players = Player.all end endビューで表示
<% @players.each do |player| %> <li style="list-style: none"> <%= player.number %> <%= player.player_name %> <%= player.hand %> <%= player.bat %> <%= player.team %> </li> <% end %>4.結果
- 投稿日:2021-03-05T17:30:47+09:00
日付フォーマットの共通化
はじめに
日付フォーマットメソッドを共通化して適用する方法について
環境
Rails 5.2.4
やったこと
共通フォーマットの設定はここに記述します。
config/initializers/time_formats.rbTime::DATE_FORMATS[:datetime_jp] = '%Y年%m月%d日 %H時%M分'Viewに適用します。
<% @boards.each do |b| %> <tr> <th><%= f.created_at.to_s(:datetime_jp) %></th> <th><%= f.updated_at.to_s(:datetime_jp) %></th> </tr> <% end %>
- 投稿日:2021-03-05T17:21:19+09:00
【Rails】renderメソッドのcollectionオプションの使い方
環境
macOS: Big Sur Ver11.2.2
Rails: 6.0.0
Ruby: 2.6.5エラーの内容
ビューファイルで以下のようにrenderメソッドを使って部分パーシャルを呼び出したのだが、なぜかエラーが出て表示されなかった。
<div class="main-contents"> <h2 class="main-title">新規登録店舗</h2> <div class="contents-bar"> <%= render partial: "static_pages/new_users", collection: @users %> </div> </div>解決策
通常なら上記の記述でパーシャルファイルでは「user」として変数を利用できるが、なぜか変数がうまく渡っていなかった模様。
試しに以下の記述を追記したら無事に表示された。<div class="main-contents"> <h2 class="main-title">新規登録店舗</h2> <div class="contents-bar"> <%= render partial: "static_pages/new_users", collection: @users, :as => "user" %> </div> </div>「as」でパーシャルファイルで使う変数名を明確に指定しただけ。
とりあえず表示されて一安心です!
- 投稿日:2021-03-05T17:20:16+09:00
Dockerによる開発環境を立ち上げるまで【Rails + Nginx + Unicorn + MySQL】
Dockerを導入し、コンテナを立ち上げVSCodeで開発を行うところまで環境構築ができました。
備忘として導入した流れを簡単にまとめておきたいと思います。インストール
Docker公式サイトからDocker Desctopをダウンロード。
以下の記事を参考にしてインストールしました。フォルダ構成
Dockerfileは記事によって配置場所が異なりますが、個人的にはこの構成が一番綺麗にまとまってると思いました。
Dockerfileやdocker-compose.yml、それにNginxやUnicornの設定ファイルは自動生成されないので自分で作ります。app ├ config │ ├ database.yml │ └ unicorn_development.conf.rb ├ docker │ ├ web │ │ └ Dockerfile │ └ nginx │ ├ Dockerfile │ └ revorite.conf └ docker-compose.ymlDocker関連ファイルの記述
以下記事を参考にさせていただきました。
参考記事:DockerでNginx+unicorn+rails+Mysqlの開発環境を作ってみたdocker/web/Dockerfile
FROM ruby:2.7.1 # dockerizeパッケージダウンロード用環境変数 ENV DOCKERIZE_VERSION v0.6.1 # パッケージの取得 RUN apt-get update && \ apt-get install -y --no-install-recommends\ nodejs \ vim \ mariadb-client \ build-essential \ wget \ && wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ && tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY Gemfile /app/Gemfile COPY Gemfile.lock /app/Gemfile.lock RUN echo "alias cp='cp -i'" >> /root/.bashrc RUN echo "alias mv='mv -i'" >> /root/.bashrc RUN echo "alias rm='rm -i'" >> /root/.bashrc RUN echo "alias la='ls -al'" >> /root/.bashrc RUN echo "alias ll='ls -l'" >> /root/.bashrc RUN gem install bundler RUN bundle install COPY . /appdocker/nginx/Dockerfile
FROM nginx:stable # デフォルトのNginxの設定ファイルを削除し、作成しておいた設定ファイルをコピー RUN rm -f /etc/nginx/conf.d/* COPY ./docker/nginx/revorite.conf /etc/nginx/conf.d/revorite.conf RUN echo "alias cp='cp -i'" >> /root/.bashrc RUN echo "alias mv='mv -i'" >> /root/.bashrc RUN echo "alias rm='rm -i'" >> /root/.bashrc RUN echo "alias la='ls -al'" >> /root/.bashrc RUN echo "alias ll='ls -l'" >> /root/.bashrc # -c以降の設定ファイルを指定して起動 daemon offでフォアグラウンドで起動 CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.confdocker/nginx/revorite.conf (Nginx設定ファイル)
docker/nginx/revorite.conf# log directory error_log /var/log/nginx.error.log; access_log /var/log/nginx.access.log; # max body size client_max_body_size 2G; upstream unicorn { # for UNIX domain socket setups server unix:/app/tmp/sockets/.unicorn.sock fail_timeout=0; } server { listen 80; server_name localhost; # nginx so increasing this is generally safe... keepalive_timeout 5; # path for static files root /app/public; location @unicorn { # HTTP headers proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass http://unicorn; } location / { try_files $uri/index.html $uri.html $uri @unicorn; include /etc/nginx/mime.types; } location ~ ^/assets/(.*) { alias /app/public/assets/$1; } # Rails error pages error_page 500 502 503 504 /500.html; location = /500.html { root /app/public; } }docker-compose.yml
docker-compose.ymlversion: '3' services: web: build: context: . dockerfile: ./docker/web/Dockerfile # dockerizeを使い、DBの起動を待ってからUnicornを起動する。 command: dockerize -wait tcp://db:3306 -timeout 20s bundle exec unicorn -p 3000 -c /app/config/unicorn_development.conf.rb environment: TZ: Asia/Tokyo tty: true # binding.pryを利用可能にするための2行 stdin_open: true # binding.pryを利用可能にするための2行 depends_on: - db ports: - "3000:3000" volumes: - .:/app:cached # ソケット通信用ファイルをnginxコンテナと共有 - tmp-data:/app/tmp/sockets # アセットファイルをnginxと共有 - public-data:/app/public db: image: mysql:5.7 command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci ports: - "4306:3306" environment: MYSQL_DATABASE: revorite_development MYSQL_ROOT_PASSWORD: password # dbのデータを永続化しておく volumes: - mysql-data:/var/lib/mysql nginx: build: context: . dockerfile: ./docker/nginx/Dockerfile ports: - 80:80 restart: always #明示的にstopさせるまでリスタートする。(失敗するたび遅延あり) volumes: - tmp-data:/app/tmp/sockets - public-data:/app/public depends_on: - web volumes: public-data: tmp-data: mysql-data:config/database.yml
database.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> development: <<: *default host: db username: root password: password database: revorite_developmentconfig/unicorn_development.conf.rb
config/unicorn_development.conf.rb# set lets $worker = 2 $timeout = 30 $app_dir = "/app" $listen = File.expand_path 'tmp/sockets/.unicorn.sock', $app_dir $pid = File.expand_path 'tmp/pids/unicorn.pid', $app_dir $std_log = File.expand_path 'log/unicorn.log', $app_dir # set config worker_processes $worker working_directory $app_dir stderr_path $std_log stdout_path $std_log timeout $timeout listen $listen pid $pid # loading booster preload_app true # before starting processes before_fork do |server, worker| defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect! old_pid = "#{server.config[:pid]}.oldbin" if old_pid != server.pid begin Process.kill "QUIT", File.read(old_pid).to_i rescue Errno::ENOENT, Errno::ESRCH end end end # after finishing processes after_fork do |server, worker| defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection endコンテナ作成・起動
$ docker-compose build # イメージの作成 $ docker-compose up -d # コンテナの作成・起動詰まった点
他の記事ではあまり語られておらず、自分が詰まった点をいくつか。
Unicornの起動で失敗する
関連ファイルを作り終え、いざ
docker-compose build
とdocker-compose up -d
を叩く。
そしてdocker ps
を叩くと・・・CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 11e605b70c51 revorite_nginx "/docker-entrypoint.…" 21 minutes ago Up 21 minutes 0.0.0.0:80->80/tcp revorite_nginx_1 313d53cbbc27 mysql:5.7 "docker-entrypoint.s…" 21 minutes ago Up 21 minutes 33060/tcp, 0.0.0.0:4306->3306/tcp revorite_db_1あれ?アプリサーバは???
で、docker-compose logs
でログを見てみるとweb_1 | 2021/03/04 11:01:27 Command exited with error: exit status 1
exit status 1
とは一体・・・
exitしたということは一度起動して即落ちた、と読めるので、落ちたコンテナに入ってログも見てみたのですがそれらしいログは出ていない。
(落ちたコンテナに入る方法はこちらを参考にしました)最終的に、
database.yml
にhost: db
の記述が漏れているためだということが判明。一文追加し、無事起動しました。
こんな単純なミスなのですが一日使ってしまったので、Dockerfileとdocker-composeだけを書いて満足しないように注意です。コンテナ内でGitが使えない
GitをインストールしSSH認証もできるようにしたのですが、どうしてもgit pushだけが弾かれてしまうんですよね。(commitまではできる)
git@github.com: Permission denied (publickey). fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists.仕方ないのでgit関連の操作はホスト側で実行しています(ホスト側でコンテナ内のソースコードに対しマウントしている)。git操作もコンテナ内で完結できたらベストだったのですが。
コンテナ内で開発を行う(VSCode: Remote Development)
コンテナ内で開発を行うには、VSCodeでコンテナ内のソースを参照・開発できるRemote Developmentという拡張機能を使います。
参考記事:VS Code Remote Development で Docker 開発環境を利用する前述の起動コマンドで起動できていれば、参考記事の通りに起動するだけで特に問題は起きないはず。
その他補足
docker関連のコマンドは長いので、~/.bashrcにエイリアスを設定すると便利。
またエラーが起きる度にコンテナやイメージの削除コマンドを打つのが大変のため、こちらも設定しておくと快適です。# 例 alias dco='docker-compose' alias docker-purge='docker stop $(docker ps -q) && docker rm $(docker ps -a -q) && docker rmi $(docker images -q -a) -f'改善点や誤りなどありましたらコメントで指摘いただけると幸いです。
- 投稿日:2021-03-05T16:43:57+09:00
【Rails + JavaScript】投稿画面に画像プレビュー機能を実装しよう!
画像を投稿する際に選択した画像がプレビューできる機能を実装していきます。
今回も初心者向けにレシピ投稿アプリを例に作成していきます。JavaScript初心者にもわかりやすいようにメソッドやイベントについては外部リンクを参照しながら解説していきます。
画像投稿機能実装については前回記事にしておりますので、そちらを参照してください。
【超かんたん】Active Storageで画像投稿機能を実装しよう!完成イメージ
事前準備
Javascriptファイルの作成
まずは、プレビュー機能を実装するためのpreview.jsを作成します。
ターミナルtouch app/javascript/packs/preview.jsファイルが作成できたらpreview.jsを読み込むための記述をapplication.jsにしていきます。
また、turbolinksはコメントアウトします。app/javascript/packs/application.js// This file is automatically compiled by Webpack, along with any other files // present in this directory. You're encouraged to place your actual application logic in // a relevant structure within app/javascript and only use these pack files to reference // that code so it'll be compiled. require("@rails/ujs").start() // require("turbolinks").start() // コメントアウト require("@rails/activestorage").start() require("channels") require('./preview') // 追記 //以下略画像を表示するスペースの作成
画像を表示する場所をビューファイルに指定します。
app/views/recipes/new.html.erb<%= form_with model: @recipe, local: true do |f| %> #中略 <div class="form-group"> <label class="text-secondary">画像</label><br> <%= f.file_field :image %> </div> <div id="new-image"></div> #追記 #以下略 <% end %>Javascriptファイルの編集
preview.jsを新規投稿ページでしか発火しないようにif文を作成します。
app/javascript/packs/preview.jsif (document.URL.match(/new/)){ }次にHTMLが最初に読み込まれたときに作動する関数を定義していきます。
addEventListenerメソッドとDOMContentLoadedイベントを使います。addEventListenerの使い方は下記の通りです。
要素.addEventListener(イベント, 関数, オプション);それでは処理を記述していきます。
app/javascript/packs/preview.jsif (document.URL.match(/new/)){ document.addEventListener('DOMContentLoaded', () => { }); }続いて、検証ツールを用いて投稿フォームのファイル選択ボックスのidを確認しましょう。
今回のレシピアプリの場合はid="recipe_image"だったのでこのrecipe_imageをgetElementByIdメソッドで取得していきます。
そして、投稿フォームのファイル選択ボックスに変化(change)が起こったときに行われる処理を記述していきます。app/javascript/packs/preview.jsif (document.URL.match(/new/)){ document.addEventListener('DOMContentLoaded', () => { document.getElementById('recipe_image').addEventListener('change', (e) =>{ console.log(e); }); }); }アロー関数の「e」はgetElementByIdで取得した投稿フォームのファイル選択ボックスの中身になります。(eはeventの略)
では、本当に中身が取得できたいるか確かめて見ましょう。
以下のようにコンソールに出力されていれば成功です。
では、取得した情報を定数に格納します。
e.target.files[0]で取得したファイルの情報を定数fileに格納し、URL.createObjectURL(file)で取得した情報を文字列に変換し、定数blobに格納します。
そして、blobを引数にcreateImageHTML( )という関数を呼び出します。(createImageHTML( )はこのあと作成します。)
app/javascript/packs/preview.jsif (document.URL.match(/new/)){ document.addEventListener('DOMContentLoaded', () => { document.getElementById('recipe_image').addEventListener('change', (e) => { console.log(e); //削除 //ここから追記 const file = e.target.files[0]; const blob = window.URL.createObjectURL(file); createImageHTML(blob); //ここまで追記 }); }); }それでは、createImageHTML( )を作成しましょう。
まずは、getElementByIdでnew.html.erbに先ほど追加したdiv要素のidのnew-imageを取得します。app/javascript/packs/preview.jsif (document.URL.match(/new/)){ document.addEventListener('DOMContentLoaded', () => { //ここから追記 const createImageHTML = (blob) => { const imageElement = document.getElementById('new-image'); }; //ここまで追記 document.getElementById('recipe_image').addEventListener('change', (e) => { const file = e.target.files[0]; const blob = window.URL.createObjectURL(file); createImageHTML(blob); }); }); }次にcreateElementメソッドでHTML要素の「img」を作成し、blobImageに格納します。
そして、setAttributeでclassとsrcをimgに付与します。
classを付与しているのはCSSを当てるためです。setAttributeの使い方は以下の通りです。
要素.setAttribute("データ名",データ);以上のことを踏まえて、記述していきましょう。
app/javascript/packs/preview.jsif (document.URL.match(/new/)){ document.addEventListener('DOMContentLoaded', () => { const createImageHTML = (blob) => { const imageElement = document.getElementById('new-image'); //ここから追記 const blobImage = document.createElement('img'); blobImage.setAttribute('class', 'new-img') blobImage.setAttribute('src', blob); //ここまで追記 }; document.getElementById('recipe_image').addEventListener('change', (e) => { const file = e.target.files[0]; const blob = window.URL.createObjectURL(file); createImageHTML(blob); }); }); }最後に、appendChildメソッドを使ってnew.html.erbに追加したdiv要素の中にimg要素を入れます。
appendChildの使い方は以下の通りです。
親要素.appendChild(追加する子要素);それでは、preview.jsに追記しましょう。
app/javascript/packs/preview.jsif (document.URL.match(/new/)){ document.addEventListener('DOMContentLoaded', () => { const createImageHTML = (blob) => { const imageElement = document.getElementById('new-image'); const blobImage = document.createElement('img'); blobImage.setAttribute('class', 'new-img') blobImage.setAttribute('src', blob); imageElement.appendChild(blobImage); //追記 }; //以下省略setAttributeで付与したクラス「new-img」にCSSをあてます。
style.css.new-img{ width: 400px; object-fit: cover; }実際にビューを確認してみましょう。
画像が表示され以下のようにimg要素にclass属性とsrc属性がセットされていれば成功です。
既存のプレビューを削除しよう
現状だと画像ファイルを選択し直すとどんどん画像がプレビューされていくという問題点があります。
この問題を解決していきましょう。querySelectorメソッドを使ってimg要素を取得し、imageContentに格納します。
そして、if文を使いimageContentに値が入っている場合removeされます。
(img要素がない、つまりimageContentがnullの場合はif文がfalseとなり、実行されません。)app/javascript/packs/preview.js//中略 document.getElementById('recipe_image').addEventListener('change', (e) => { //ここから追記 const imageContent = document.querySelector('img'); if (imageContent){ imageContent.remove(); } //ここまで追記 const file = e.target.files[0]; const blob = window.URL.createObjectURL(file); createImageHTML(blob); }); }); }それでは確認してみましょう。
以下の通り、画像ファイルを選択し直すと再プレビューされれば成功です。
以下、完成形のコードです。
app/javascript/packs/preview.jsif (document.URL.match(/new/)){ document.addEventListener('DOMContentLoaded', () => { const createImageHTML = (blob) => { const imageElement = document.getElementById('new-image'); const blobImage = document.createElement('img'); blobImage.setAttribute('class', 'new-img') blobImage.setAttribute('src', blob); imageElement.appendChild(blobImage); }; document.getElementById('recipe_image').addEventListener('change', (e) => { const imageContent = document.querySelector('img'); if (imageContent){ imageContent.remove(); } const file = e.target.files[0]; const blob = window.URL.createObjectURL(file); createImageHTML(blob); }); }); }
- 投稿日:2021-03-05T16:42:02+09:00
"共働き夫婦特化型"の家計簿作りました!〜Common Wallet(コモンウォレット)〜
はじめに
※「はじめに」では私の家計簿への愛が無駄に長く語られます。
面倒な人は次章までお飛ばし下さい。・共働き夫婦の家庭に質問です。
「どのように家計を管理してますか?」
A)一部または全部のお金を渡し、片方が管理(いわゆる「お小遣い制」)
B)それぞれ自分の財布で管理して、家賃は旦那、食費は嫁など項目ごとに分担する
C)共通財布を用意し、毎月お金をお互い入れて、そこで生活費をやりくりする。
だいたいこの3つでしょうか?ちなみにリクルートブライダル総研が行った「新婚生活実態調査」によると、
共働き家庭では、Aが50.5%、Cが33.0%、最後にBが14.2%と、
Aが半数以上を締めているらしいです。(ちなみにうちの両親もAです。)
※上記調査を基に筆者作成しかもAの中でも「妻が管理」が46.8%、「夫が管理」はわずか3.7%と、妻が殆ど、、、
つまり世の中の財布は大半を妻が握っているのです!!私は世の旦那に問いかけたい、、、
「それで良いんですか?」
と。
自分の稼いだお金がいつのまにか搾取されていることも気づかず、
いつまでも妻に尻に敷かれてて良いんですか!!(世のお小遣い制の男性陣どうか叩かないで・・・)そういう意味で私はBも良いなと思ってます。完全に個人管理ですし、何より楽ですし。
ただ、生活費の負担額は極力平等に、そして公開されている方がお金に対する不満はなくなるかな、とも思い、
うちではCの共通財布制を採用しています。
不満があれば家計簿を見れば一発で原因がわかります。
実際うちではお金の喧嘩は一度もしたことないから、共通財布の家計簿管理は夫婦仲的にもおすすめです!笑ただ、
共通財布って管理がものすごく面倒なんです。というのも、家計簿って逐一入力するのさえ面倒ですよね?
共通財布だとそれに加えて、たまに個人の財布から立替えたりすることもあるので、その立替処理も記録してるともうぐっちゃぐちゃになるんです。
それに、毎月の入金額も給料から色々計算したい場合、それも別途で管理しているともう何がなんだかわからなくなります。私も色んな家計簿を試してみましたが、上記の悩みを解決する家計簿が見つからず、
仕方なく、自作のエクセルで管理してました。しかし!今や私も半人前ながらプログラミングを勉強している身!
プログラマーならそのアプリを作ってしまおう、ということで作りました!!
スマホでも操作しやすいようにしたかったですし。名付けて「Common Wallet(コモンウォレット)」共通財布専用家計簿アプリです。
共通財布家計簿「Common Wallet(コモンウォレット)」
以下が私が今回作ったアプリです。
Common Wallet
①基本的な家計簿機能
通常の家計簿アプリにある機能
「明細入力」「残高管理」「予算管理」等は実装してあるので、
共通財布ではなく個人の家計簿として利用することもできます。
②データ分析
支出と予算を入力すると、
分析画面から「項目別支出」や「予算対比」など各種データを閲覧できます。
また家計簿一覧ページでは、
月次の残高推移もグラフで確認できます。
③精算機能
精算画面で精算未済の明細について
自動的に集計して精算額を算出してくれます。
精算対象はステータスが「未済」かつ今日以前の日付のものになります。
また、精算画面については、
ホーム画面の精算未済欄からも飛ぶことができます。
④入金計算
毎月の入金金額を算出してくれます。
算出方法は「可処分所得均等法(私が勝手に命名笑)」を採用しており、
生活費を差し引いた残りの手取りが夫婦同額になるような入金額を計算してくれます。
ただしこの場合、給料を多くもらっている方が、かなり多く負担するケースがあるので、補正項目を追加しています。
補正基準については各家庭で決めてもらっていいと思います。
技術面
僕は未熟者なので大した技術は使えていないですが、
苦労した箇所の参考記事は勉強中の方にも参考になると思うのでご紹介します。
rails使ってます。Highcharts
本サービスで使っているグラフはchartkickの「Highcharts」を使っています。
chartkickでは他にも、chart-js、 Google Chartsが使えますが、
Highchartsが微調整も利きやすく、そしてきれいな印象です。
この記事にすごくわかりやすい作成例があったので、参考にしました。
chartkick × Highchartsでドーナツグラフを作るAjax処理
初めて実践でAjax処理をしたのでだいぶ苦労しました。。。
そもそもjsの書き方とか知らないし。。。
でも何度も追加や削除を行う画面ではAjax化しないとかなりUX的にマイナスだと思うので、
使うべきだなと感じました。
以下の記事を読んでAjaxの理解が進みました。
Ruby on RailsのAjax処理のおさらい
RailsにおけるAjaxの実装(JavaScriptとjQueryのコード比較)AWSデプロイ
初めて作った(クソ)アプリ「笑うぎんこうまん」はherokuが全部勝手にデプロイしてくれたので、超絶楽チンでした。
でも、「まともなサービスなら独自ドメインもしっかり取りたいし、しっかり勉強もしたい!」とか意気込んでAWSに挑戦してみましたが、しっかり爆死。。。
用語全てが意味不明でだいぶしんどかったですが、構築知識は身についたなと思います。
ちなみにAWSデプロイは以下の記事がすごく丁寧でわかりやすいです。
世界一丁寧なAWS解説レスポンシブデザイン
今回は「スマホでも操作しやすい」を目的としていたので、
レスポンシブデザインにもところどころこだわりました。
例えば明細一覧画面。大きい画面では収入・支出が別々に記載されていますが、
スマホサイズでは「収支」とまとめて書かれています。○パソコンサイズ
○スマホサイズ
これはCSSでサイズごとに文字を「display:none;」して非表示にしています。
以下の記事を参考にしました。
【レスポンシブデザイン】不要な要素をCSSだけで非表示にする方法
ただし記事にもある通り、
「display:none;」はSEO的にリスクがあるそうなのでご利用は計画的に笑おわりに
以上が今回私の作ったアプリの説明です。
正直ここまで細かく家計簿を作る人もそうそういないと思うので、かなりニッチなサービスですが、、、笑良ければ使って下さい。
- 投稿日:2021-03-05T14:37:33+09:00
【Ruby on Rails】OmniAuthのGitHub認証にて近似エラーメッセージが出て一苦労
環境
MacOS 11.2.1
Ruby 3.0.0
Rails 6.1.0
devise 4.7.3
omniauth 1.9.1
omniauth-github 1.4.0
omniauth-oauth2 1.7.1問題
OmniAuthの概ねの実装を行ったがGitHubの認証用APIにPOSTしている時点でコケてしまう。
結論
以下2パターンの間違いがほぼ同じエラーを出していた。エラーが似ているので、片方が間違えたまま、もう片方を修正しても動作確認でわからない。
- OAuth AppsとGitHub Appsを間違える(詳細は下記)
- token_params: { parse: :json }をDeviseのconfigに設定する(詳細は下記)解決方法
本当はログをよくチェックするべきだったと思います。
自分の場合は、POSTのあと”Invalid Credential”と出ているので、Deviseの設定が怪しいと思って一つ一つ直しながらひたすら動作確認をしていました。OAuth AppsとGitHub Appsを間違える、とは
developer settingsによく似た二つの項目がある。
GitHub AppsとOAuth AppsOmniAuthでGitHub認証で必要なのはOAuthAppsで生成するIDとSECRET。
まちがえてGitHubAppsで生成するIDとSECRETを使うと以下のような現象が起こる。1度目のみ認証画面になる
認証してもログインできず、2度目以降は認証画面は出ず、ログインに失敗しつづける
ログ
1回目48826 Started POST "/users/auth/github" for ::1 at 2021-03-05 10:17:35 +0900 48827 Started GET "/users/auth/github/callback?code=xxx&state=xxx" for ::1 at 2021-03-05 10:20:24 +0900 48828 Processing by Users::OmniauthCallbacksController#failure as HTML 48829 Parameters: {"code"=>"xxx", "state"=>"xxx"} 48830 Redirected to http://localhost:3000/ 48831 Completed 302 Found in 3ms (ActiveRecord: 0.0ms | Allocations: 334)2回目以降
48876 Started POST "/users/auth/github" for ::1 at 2021-03-05 10:30:39 +0900 48877 Started GET "/users/auth/github/callback?code=xxx&state=xxx" for ::1 at 2021-03-05 10:30:39 +0900 48878 Processing by Users::OmniauthCallbacksController#failure as HTML 48879 Parameters: {"code"=>"xxx", "state"=>"xxx"} 48880 Redirected to http://localhost:3000/ 48881 Completed 302 Found in 1ms (ActiveRecord: 0.0ms | Allocations: 205)コンソール
Started POST "/users/auth/github" for ::1 at 2021-03-05 10:30:39 +0900 I, [2021-03-05T10:30:39.445975 #78131] INFO -- omniauth: (github) Request phase initiated. Started GET "/users/auth/github/callback?code=xxx&state=xxx" for ::1 at 2021-03-05 10:30:39 +0900 I, [2021-03-05T10:30:39.860370 #78131] INFO -- omniauth: (github) Callback phase initiated. E, [2021-03-05T10:30:40.842629 #78131] ERROR -- omniauth: (github) Authentication failure! invalid_credentials: OAuth2::Error, : {"message":"Resource not accessible by integration","documentation_url":"https://docs.github.com/rest/reference/users#list-email-addresses-for-the-authenticated-user"} Processing by Users::OmniauthCallbacksController#failure as HTML Parameters: {"code"=>”xxx”, "state"=>”xxx”} Redirected to http://localhost:3000/ Completed 302 Found in 1ms (ActiveRecord: 0.0ms | Allocations: 205)コンソールの方のログを見ると
Authentication failure! invalid_credentials
となっているtoken_params: { parse: :json }をDeviseのconfigに設定する、とは
Facebook認証をする場合、あるバージョン以降、レスポンスフォーマットがJSONに変わっているので、それをパースする処理を加える必要があるそうな。
https://qiita.com/anoworl/items/ea04d941d5d2bdea0e66
config/initializers/devise.rb
config.omniauth :github, ENV["GITHUB_ID"], ENV["GITHUB_SECRET"], token_params: { parse: :json }
token_params: { parse: :json }
の部分を加えるとJSONをパースしてくれるGitHub認証には必要ない設定で、入れるとエラーになってしまう。
入れた場合、POST直後に認証失敗してログインできない。
エラーログ
49561 Started POST "/users/auth/github" for ::1 at 2021-03-05 10:46:03 +0900 49562 Started GET "/users/auth/github/callback?code=xxx&state=xxx" for ::1 at 2021-03-05 10:46:04 +0900 49563 Processing by Users::OmniauthCallbacksController#failure as HTML 49564 Parameters: {"code"=>"xxx", "state"=>"xxx"} 49565 Redirected to http://localhost:3000/ 49566 Completed 302 Found in 1ms (ActiveRecord: 0.0ms | Allocations: 207)コンソール
Started POST "/users/auth/github" for ::1 at 2021-03-05 10:46:03 +0900 I, [2021-03-05T10:46:03.756773 #78764] INFO -- omniauth: (github) Request phase initiated. Started GET "/users/auth/github/callback?code=xxx&state=xxx" for ::1 at 2021-03-05 10:46:04 +0900 I, [2021-03-05T10:46:04.210471 #78764] INFO -- omniauth: (github) Callback phase initiated. E, [2021-03-05T10:46:04.593857 #78764] ERROR -- omniauth: (github) Authentication failure! invalid_credentials: OAuth2::Error, access_token=xxx&scope=user%3Aemail&token_type=bearer Processing by Users::OmniauthCallbacksController#failure as HTML Parameters: {"code"=>"xxx", "state"=>"xxx"} Redirected to http://localhost:3000/ Completed 302 Found in 1ms (ActiveRecord: 0.0ms | Allocations: 207)コンソールの方のログを見ると
Authentication failure! invalid_credentials
となっている
二つの現象で非常によく似たエラーが出るので原因追求が難しかった。
しかしよくみると、GitHub APPに取り違えた方のエラーメッセージには{"message":"Resource not accessible by integration","documentation_url":"https://docs.github.com/rest/reference/users#list-email-addresses-for-the-authenticated-user”}
という情報が書いてある。この部分に気づければもっと早く解決していたかもしれない。ちなみに単純にパスワードを間違えると
以下のログでPOSTしたまま以下の画面になるので動作確認ですぐに気づける
48807 Started POST "/users/auth/github" for ::1 at 2021-03-05 09:48:08 +0900
- 投稿日:2021-03-05T14:37:33+09:00
【Ruby on Rails】OmniAuthのGitHub認証、APIにPOST時点でのエラー
環境
MacOS 11.2.1
Ruby 3.0.0
Rails 6.1.0
devise 4.7.3
omniauth 1.9.1
omniauth-github 1.4.0
omniauth-oauth2 1.7.1過程
deviseでユーザー認証機能を導入した後、下記のサイトにのっとってGitHub認証を導入する。
https://github.com/heartcombo/devise/wiki/OmniAuth:-Overview問題
概ねの実装を行ったがAPIにPOSTしている時点でコケてしまう。
結論
以下2パターンの間違いがほぼ同じエラーを出していた。エラーが似ているので、片方が間違えたまま、もう片方を修正しても動作確認でわからない。
- OAuth AppsとGitHub Appsを間違える(詳細は下記)
- token_params: { parse: :json }をDeviseのconfigに設定する(詳細は下記)解決方法
本当はログをよくチェックするべきだったと思います。
自分の場合は、POSTのあと”Invalid Credential”と出ているので、Deviseの設定が怪しいと思って一つ一つ直しながらひたすら動作確認をしていました。OAuth AppsとGitHub Appsを間違える、とは
developer settingsによく似た二つの項目がある。
GitHub AppsとOAuth AppsOmniAuthでGitHub認証で必要なのはOAuthAppsで生成するIDとSECRET。
まちがえてGitHubAppsで生成するIDとSECRETを使うと以下のような現象が起こる。1度目のみ認証画面になる
認証してもログインできず、2度目以降は認証画面は出ず、ログインに失敗しつづける
ログ
1回目48826 Started POST "/users/auth/github" for ::1 at 2021-03-05 10:17:35 +0900 48827 Started GET "/users/auth/github/callback?code=xxx&state=xxx" for ::1 at 2021-03-05 10:20:24 +0900 48828 Processing by Users::OmniauthCallbacksController#failure as HTML 48829 Parameters: {"code"=>"xxx", "state"=>"xxx"} 48830 Redirected to http://localhost:3000/ 48831 Completed 302 Found in 3ms (ActiveRecord: 0.0ms | Allocations: 334)2回目以降
48876 Started POST "/users/auth/github" for ::1 at 2021-03-05 10:30:39 +0900 48877 Started GET "/users/auth/github/callback?code=xxx&state=xxx" for ::1 at 2021-03-05 10:30:39 +0900 48878 Processing by Users::OmniauthCallbacksController#failure as HTML 48879 Parameters: {"code"=>"xxx", "state"=>"xxx"} 48880 Redirected to http://localhost:3000/ 48881 Completed 302 Found in 1ms (ActiveRecord: 0.0ms | Allocations: 205)コンソール
Started POST "/users/auth/github" for ::1 at 2021-03-05 10:30:39 +0900 I, [2021-03-05T10:30:39.445975 #78131] INFO -- omniauth: (github) Request phase initiated. Started GET "/users/auth/github/callback?code=xxx&state=xxx" for ::1 at 2021-03-05 10:30:39 +0900 I, [2021-03-05T10:30:39.860370 #78131] INFO -- omniauth: (github) Callback phase initiated. E, [2021-03-05T10:30:40.842629 #78131] ERROR -- omniauth: (github) Authentication failure! invalid_credentials: OAuth2::Error, : {"message":"Resource not accessible by integration","documentation_url":"https://docs.github.com/rest/reference/users#list-email-addresses-for-the-authenticated-user"} Processing by Users::OmniauthCallbacksController#failure as HTML Parameters: {"code"=>”xxx”, "state"=>”xxx”} Redirected to http://localhost:3000/ Completed 302 Found in 1ms (ActiveRecord: 0.0ms | Allocations: 205)コンソールの方のログを見ると
Authentication failure! invalid_credentials
となっているtoken_params: { parse: :json }をDeviseのconfigに設定する、とは
Facebook認証をする場合、あるバージョン以降、レスポンスフォーマットがJSONに変わっているので、それをパースする処理を加える必要があるそうな。
https://qiita.com/anoworl/items/ea04d941d5d2bdea0e66
config/initializers/devise.rb
config.omniauth :github, ENV["GITHUB_ID"], ENV["GITHUB_SECRET"], token_params: { parse: :json }
token_params: { parse: :json }
の部分を加えるとJSONをパースしてくれるGitHub認証には必要ない設定で、入れるとエラーになってしまう。
入れた場合、POST直後に認証失敗してログインできない。
エラーログ
49561 Started POST "/users/auth/github" for ::1 at 2021-03-05 10:46:03 +0900 49562 Started GET "/users/auth/github/callback?code=xxx&state=xxx" for ::1 at 2021-03-05 10:46:04 +0900 49563 Processing by Users::OmniauthCallbacksController#failure as HTML 49564 Parameters: {"code"=>"xxx", "state"=>"xxx"} 49565 Redirected to http://localhost:3000/ 49566 Completed 302 Found in 1ms (ActiveRecord: 0.0ms | Allocations: 207)コンソール
Started POST "/users/auth/github" for ::1 at 2021-03-05 10:46:03 +0900 I, [2021-03-05T10:46:03.756773 #78764] INFO -- omniauth: (github) Request phase initiated. Started GET "/users/auth/github/callback?code=xxx&state=xxx" for ::1 at 2021-03-05 10:46:04 +0900 I, [2021-03-05T10:46:04.210471 #78764] INFO -- omniauth: (github) Callback phase initiated. E, [2021-03-05T10:46:04.593857 #78764] ERROR -- omniauth: (github) Authentication failure! invalid_credentials: OAuth2::Error, access_token=xxx&scope=user%3Aemail&token_type=bearer Processing by Users::OmniauthCallbacksController#failure as HTML Parameters: {"code"=>"xxx", "state"=>"xxx"} Redirected to http://localhost:3000/ Completed 302 Found in 1ms (ActiveRecord: 0.0ms | Allocations: 207)コンソールの方のログを見ると
Authentication failure! invalid_credentials
となっている
二つの現象で非常によく似たエラーが出るので原因追求が難しかった。
しかしよくみると、GitHub APPに取り違えた方のエラーメッセージには{"message":"Resource not accessible by integration","documentation_url":"https://docs.github.com/rest/reference/users#list-email-addresses-for-the-authenticated-user”}
という情報が書いてある。この部分に気づければもっと早く解決していたかもしれない。ちなみに単純にパスワードを間違えると
以下のログでPOSTしたまま以下の画面になるので動作確認ですぐに気づける
48807 Started POST "/users/auth/github" for ::1 at 2021-03-05 09:48:08 +0900
- 投稿日:2021-03-05T14:01:17+09:00
Railsポートフォリオ作成 #6 基本機能フロントエンド開発
こんにちは
今回は基本機能のフロントエンド開発を行いました。
(前回記事(#5 基本機能バックエンド開発))私は、前職(ホテルの料飲部)における、コミュニケーションの課題を解決するアプリを作っています。
具体的には、「レストランで使うものの数を管理するアプリ」です。今回は、基本機能のフロントエンド開発を行いました
フロントエンドは経験値が低いので、なんとか"見れる見た目"にするので精一杯でした。
ここから、ポートフォリオとして恥ずかしくない見た目にする自信が正直ないです。
正直、途中で挫折するかも、、、
とか思ってしまいます、、、他の方のポートフォリオを拝見したのですが、レベルが高すぎて言葉を失いました。
前回仕組みで解決したいと書いたのですが、まだ完全にはうまくいっていません。
まだまだ改善が必要です。感じたこと
知識は必要な時に身につけるべき
以前時間があった時に、後からいるからと思ってHTML・CSSの復習をしていたのですが、結局また少し復習することになってしまい、二度手間になってしまいました。
ちなみに、これをTwitterで呟いたところ、今までで一番いいね等をゲットしたツイートになりました。レスポンシブにしないと、、、
パソコンで作っているので、パソコンで合うようになっていて、スマホで見た際に見づらくなっていないかと、スマホでもチェックしながらやらないといけないと思いました。とりあえず、なんとか見れるようにはなったので、次に進もうと思います。
次はDockerです
「開発環境でDockerとdocker-composeを導入してみる」
ということを目標に、取り組んでいきたいと思います。
フロントエンドが終わり、第一段階が終了したので、第二段階に進みたいと思います。
まずはDockerの導入をしていきたいと思います
勉強するところからなので、時間がかかると思いますが、少しずつやっていきたいと思います。
- 投稿日:2021-03-05T11:59:17+09:00
RailsのDockerイメージの作り方
0. はじめに
以下のようなディレクトリ構成にします。
. ├── app/ # Railsアプリ └── docker/ # Dockerfile置き場 └── app/ └── Dockerfile1. ローカル環境にRailsをインストール
% rails new app
2. Dockerfileの作成
docker/app/Dockerfile# syntax=docker/dockerfile:1.2 # base FROM ruby:3.0.0-alpine3.13 AS base WORKDIR /app RUN \ --mount=type=cache,target=/var/cache/apk \ apk add -U \ build-base \ git \ nodejs \ sqlite-dev \ tzdata \ yarn \ ; # bundle FROM base AS bundle COPY Gemfile Gemfile.lock /app/ RUN \ --mount=type=cache,target=/app/vendor/cache \ bundle install && \ bundle cache # yarn FROM base AS node_modules COPY package.json yarn.lock /app/ RUN \ --mount=type=cache,target=/usr/local/share/.cache/yarn/v4 \ yarn install # main FROM base ARG RAILS_MASTER_KEY ENV RAILS_MASTER_KEY $RAILS_MASTER_KEY COPY --from=bundle /usr/local/bundle/ /usr/local/bundle/ COPY --from=node_modules /app/node_modules/ /app/node_modules/ COPY . /app/ EXPOSE 3000 CMD ["bundle", "exec", "rails", "s", "-b", "0.0.0.0"]3. .dockerignoreの用意
不要なファイルをイメージに含めないために.dockerignoreを作成します
app/.dockerignore/.bundle/ /.dockerignore /.git/ /.git* /.ruby-version /README.md /config/master.key /log/ /node_modules/ /storage/ /tmp/ /vendor/bundle/4. Dockerイメージのビルド
% DOCKER_BUILDKIT=1 docker image build --build-arg RAILS_MASTER_KEY=<RAILS_MASTER_KEY> -t boccifarm/rails:6.1.3-ruby3.0.0-alpine3.13 -f docker/app/Dockerfile app5. Dockerイメージのプッシュ
% docker login % docker image push boccifarm/rails:6.1.3-ruby3.0.0-alpine3.13
- 投稿日:2021-03-05T11:14:10+09:00
【Ruby on Rails】コントローラーの単体テストコードについてまとめ(RSpec)
初学者です。
テストコードがなぜか大好きです。今回はコントローラーの単体テストコードについてまとめます。
私はRSpecを利用しています。コントローラーのテストコードを書く方針は
アクションにリクエストを送ったとき想定通りのレスポンスが返されるかどうかを確かめる
ことです。ちなみにモデルの単体テストコードについてはこちらにまとめています。
前提条件
- pry-railsを導入済みである
- FactoryBotを導入済みである
- RSpec導入済みである
上記については以下の記事にまとめています。
【Ruby on Rails】デバッグツール(pry-rails)
【Ruby on Rails】FactoryBotとFakerについてまとめ
【Ruby on Rails】モデルの単体テストコードについてまとめ(RSpec)準備
下記コマンドでテストコード用のファイルを生成します。
成功すればspec/requests
配下に該当のファイルが生成されます。ターミナルrails g rspec:request samplesテストコード
describe
とit
で検証すべき項目を出していきます。spec/requests/samples_spec.rbrequire 'rails_helper' describe SamplesController, type: :request do before do @sample = FactoryBot.create(:sample) end describe 'GET #index' do it 'indexアクションにリクエストすると正常にレスポンスが返ってくる' do end it 'indexアクションにリクエストするとレスポンスに投稿済みのテキストが存在する' do end it 'indexアクションにリクエストするとレスポンスに投稿済みの画像URLが存在する' do end it 'indexアクションにリクエストするとレスポンスに投稿フォームが存在する' do end end endFactoryBotは
build
ではなくcreate
にしています。
今回のテストではDBにデータが存在する必要があるのでcreateです。
テストする度にデータが作られ、終わると削除されます。便利!では
indexアクションにリクエストすると正常にレスポンスが返ってくる
のテストコードを書いていきます。spec/requests/samples_spec.rbit 'indexアクションにリクエストすると正常にレスポンスが返ってくる' do # 以下を追加 get root_path binding.pry end解説します。
get ○○_path
でどのパスにリクエストを送りたいかを書きます。
rails routes
で確認してそのパスを記述します。この状態で下記コマンドでテストコードを実行します。
ターミナルbundle exec rspec spec/requests/samples_spec.rb
binding.pry
してるので処理が途中で止まりターミナルに入力できるようになっていると思います。
下記のコマンドを入力します。ターミナルresponse.statusそうするとそのレスポンスのステータスコードが出力されます。
HTTP通信においてステータスコードは以下のようになっています。
ステータスコード 内容 100~ 処理継続中 200~ 処理成功 300~ リダイレクト 400~ クライアント側のエラー 500~ サーバーエラー つまり今回の場合は
response.status
で200
がステータスコードで返ってくればOKということです。正常に200が返ってきたらテストコードを編集します。
spec/requests/samples_spec.rbit 'indexアクションにリクエストすると正常にレスポンスが返ってくる' do get root_path # 以下に編集 expect(response.status).to eq 200 endこれでテストコードを実行して成功すればOKです。
次に
indexアクションにリクエストするとレスポンスに投稿済みのテキストが存在する
のテストコードを書いていきます。spec/requests/samples_spec.rbit 'indexアクションにリクエストするとレスポンスに投稿済みのテキストが存在する' do # 以下を追加 get root_path expect(response.body).to include(@sample.text) end解説します。
response.body
と記述してinclude
で引数の内容(今回はテキスト)が存在するかを確認してます。body
でブラウザに表示されるHTMLの情報を抜き出すことができます。これでテストコードを実行して成功すればOKです。
次に
indexアクションにリクエストするとレスポンスに投稿済みの画像URLが存在する
のテストコードを書いていきます。ほぼ先ほどと同じです。spec/requests/samples_spec.rbit 'indexアクションにリクエストするとレスポンスに投稿済みの画像URLが存在する' do get root_path expect(response.body).to include(@sample.image) endこれでテストコードを実行して成功すればOKです。
次に
indexアクションにリクエストするとレスポンスに投稿フォームが存在する
のテストコードを書いていきます。
難しく考えずにその文言が存在するかを確認できればOKなので下記のようになります。spec/requests/samples_spec.rbit 'indexアクションにリクエストするとレスポンスに投稿フォームが存在する' do get root_path expect(response.body).to include('投稿する') endこれでテストコードを実行して成功すればOKです。
他のアクションについてもほぼ書き方は同じです!
コントローラーの単体テストコードは結合テストとかぶる部分もありますよねー。以上です。
- 投稿日:2021-03-05T09:05:06+09:00
【Docker】Ruby2.6.5とRails6.0.0とmysql DockerComposeで環境構築
はじめに
Ruby2.6.5とRails6.0.0とmysqlでDockerComposeで環境構築をしたので記録として記事を書きます。
もし誰かのお役に立てたら幸いです。Docker環境でアプリケーション開発する手順
1、RailsのDockerfileを作成してDockerをbuild
2、DockerCompose.ymlを作成しコンテナを作る
3、Rails のセットアップ
4、データベースを作成する開発環境
Docker version 20.10.0
docker-compose version 1.27.41、RailsのDockerfile作成
新しいデレクトリを作成します。
今回はデレクトリ名をexamとしてます。ターミナルmkdir [新規デレクトリ名] cd [新規デレクトリ名]Dockerfile
新規で作ったデレクトリに「Dockerfile」をいう名前でDockerファイルを作成します。
VSコードをお使いの方はcode Dockerfile
でターミナルから直接ファイルの作成&移動をしてくれます。
もちろんディレクトリを呼び込んでファイルを作成しても大丈夫です。exam/DockerfileFROM ruby:2.6.5 RUN apt-get update && apt-get install -y \ build-essential \ libpq-dev \ nodejs\ yarn \ vim WORKDIR /新規デレクトリ名 COPY Gemfile Gemfile.lock /新規デレクトリ名/ RUN bundle installGemファイルを作成する
ターミナルtouch Gemfile Gemfile.lockGemfileにファイルを持ってくるソースとrailsのバージョンを記述します。
Gemfilesource 'https://rubygems.org' gem 'rails', '~>6'Dockerを建てます。
ターミナルdocker build . . . . Installing rails 6.1.2.1 Bundle complete! 1 Gemfile dependency, 42 gems now installed. Use `bundle info [gemname]` to see where a bundled gem is installed. Removing intermediate container 06448279a57c ---> c21a3ec7e3a4 Successfully built c21a3ec7e3a4 #イメージが出来てるか調べる事も出来ます。 docker images #もし使っていないイメージがあれば下記のコマンドで削除できます。 docker rmi [IMAGE ID]2、DockerCompose.ymlを作成しコンテナを作る
dockerc-compose.ymlファイルを作成し記述する
ターミナルcode docker-compose.ymlこれで新しい
docker-compose.yml
が作成されます。docker-compose.ymlversion: '3' services: web: build: . ports: - 3000:3000 volumes: - '.:/[作成したディレクトリ名]' tty: true stdin_open: true
dockerc-ompose.yml
の確認をして実行します#確認します cat docker-compose.yml #実行します docker-compose up -d #コンテナが動いてる事を確認します。 docker ps docker-compose ps #コンテナの中に入ります。 docker-compose exec web bash3、Rails のセットアップ
Railsの環境でコンテナができのでその中で
rails new
をしてセットアップして行きます。#新しいrailsを作ります rails new . --force --database=mysql --skip-bundle #一度コンテナから抜けます exit #今のコンテナとイメージを削除します docker-compose down #Downになってるか確認します docker ps #新しいイメージでコンテナを建てます docker-compose up --build -d #確認します docker-compose ps #コンテナに入ります docker-compose exec web bash #Railsを起動します rails s -b 0.0.0.0 #現段階ではエラーが出ます現段階ではデータベースの記述を Dockerfileに記述していないので、mysql2のエラーが出ると思います。
下記の対応を試して下さい。【Docker】エラー Could not find gem 'mysql2 (~> 0.5)' in any of the gem sources listed in your Gemfile
https://qiita.com/AKI3/items/8009b5218be0ad67b6b7エラーが解決したら
rails s -b 0.0.0.0
でサーバーが立ち上がります。
実際にlocalhost:3000
でアクセスします。
しかしこのままではデータベースが無いのでエラー画面が表示されます。
4、データベースを作成する。
ローカルであれば
rails db:create
をするとデータベースを作れるのですが、コンテナ内では現状の設定では作れません。
Dockerではサービス(環境)ごとにコンテナを作る事が推奨されております。
今回であればRubyとmysqlのコンテナを別々に作り、それぞれのコンテナどうしを接続する必要があります。
その為、detabase.ymlの設定ファイルを編集して正しい指定をし、docker-compose.ymlの設定ファイルの編集します。先ずdetabase.ymlを編集します。
config/detabase.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> host: db development: <<: *default database: 新規デレクトリ名_development username: root password: 省略次にdocker-compose.ymlを編集します。
docker-compose.ymlversion: '3' volumes: db-data: services: web: build: . ports: - 3000:3000 volumes: - '.:/新規デレクトリ名' environment: - 'MYSQL_ROOT_PASSWORD=1' tty: true stdin_open: true depends_on: - db links: - db db: image: mysql:8 volumes: - 'db-data:/var/lib/mysql' # このenvironmentはテスト環境でのみ使用可能 environment: MYSQL_ALLOW_EMPTY_PASSWORD: 1 # command: bundle exec rails s -p 3000 -b '0.0.0.0' #こちらはお好みでdocker-compose.ymlを編集したので新しくコンテナを作ります。
今回はweb(Raisl)とdb(mysql)2つのコンテナを作る事になります。#再びコンテナを建てます docker-compose up -d #upになってるか確認します docker-compose ps #コンテナに入ります docker-compose exec web bash #データベースを作成します rails db:create #現段階ではエラーが出ますRails側のコンテナ内で作業を進めていきます。
先程作ったRails側のコンテナとmysql側のコンテナがしっかり繋がっていればrails db:create
のコマンドは通りますが、現段階では下記のエラーが発生します。Mysql2::Error::ConnectionError: Plugin caching_sha2_password could not be loaded: /usr/lib/x86_64-linux-gnu/mariadb19/plugin/caching_sha2_password.so: cannot open shared object file: No such file or directoryこれはwebコンテナが mysql 8.0 の
caching_sha2_password
が認証方式に対応していないためだそうです。詳しくは、こちらの記事で解決できます。
https://blog.toshimaru.net/rails-on-docker-compose/コマンドだけこちらに記載します。
ターミナル#データベース側のコンテナに入る docker-compose exec db bash #mysqlを操作する mysql -u root #ユーザー一覧とその認証方式が閲覧 select User,Host,plugin from mysql.user; #root@% のユーザー設定を変更 ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY ''; #再度ユーザー一覧とその認証方式が閲覧 select User,Host,plugin from mysql.user;エラーが解決できたら最後サーバーを立ち上げます。
ターミナルrails s -b 0.0.0.0 => Booting Puma => Rails 6.1.2.1 application starting in development => Run `bin/rails server --help` for more startup options Puma starting in single mode... * Puma version: 5.2.1 (ruby 2.6.5-p114) ("Fettisdagsbulle") * Min threads: 5 * Max threads: 5 * Environment: development * PID: 29 * Listening on http://0.0.0.0:3000 Use Ctrl-C to stop . . . 省略出来ました!
localhost3000
にアクセスすると-Yay! You’re on Rails!-の画面が表示されます。
最後に
手探りの対処療法でサーバー起動まで達成しましので、これからも継続学習が必要です。
なんとか環境構築まで至りましたが、Dockerについて完全に理解できておりません、万が一情報が間違っている場合ご指摘していただけると幸いです。エンジニア初学者ですが、同じ様に悩んでる方々の助けになればと思い記事を投稿しております。
万が一情報が間違っている場合ご指摘していただけると幸いです。参考
今回Udemyの教材とやっすんのエンジニア大学を主に参考にさせていただきました。
https://www.udemy.com/course/aidocker/
https://github.com/yassun-youtube/docker-compose-sample/blob/master/docker-compose.yml
- 投稿日:2021-03-05T01:30:52+09:00
Formオブジェクトの概要
Formオブジェクトとは
Formオブジェクトとは、1つのフォームからのデータ送信で複数のモデル操作、テーブルに保存しない情報にバリデーションを設定したい場合に使います。
モデルに似た機能を持ったクラスを作り、バリデーションやデータを保存する処理を記述して実装します。
実装手順はこんな感じです。1.modelsディレクトリ直下にファイル作成、クラスを定義
2.include ActiveModel::Modelの設定
3.attr_accessorで保存したい複数のテーブルのカラム名を属性値として扱えるように記述
4.バリデーションを記述
5.データをテーブルに保存する処理を記述
6.コントローラーのnew、createアクションなどでFormオブジェクトのインスタンスを生成
7.フォーム作成の部分をFormオブジェクトのインスタンスを引数として渡す形に変更以下はコードの一例と内容についてです。
qiita.rbclass OrderPurchase #OrderモデルとPurchaseモデルの処理 include ActiveModel::Model attr_accessor #ここにバリデーションなどの記述をする def save #ここにデータを保存する記述をする end end①include ActiveModel::Model
ActiveModel::Modelをincludeすると、バリデーションなどの機能が使えるようになります。
Railsガイドにも記載がありました。
Railsガイド②attr_accesor
attr_accesorでは、モデルに対応するテーブルに設定したカラム名以外の属性を扱う時に使用します。
ゲッターとセッター(データの取得と更新)をこの記述だけでできるようになります。①でモデルの機能が使えるようしているので、②でバリデーションを設定したい属性を指定できるのだと思います。
おわりに
プログラミング初心者が備忘録としてまとめたので、間違い等あるかもしれません。
新たにわかった事や、間違いがあれば随時更新します。
- 投稿日:2021-03-05T00:01:38+09:00
【Rails】Railsサーバーが起動してもlocalhost:3000で表示されなかった症状が、解決した件について【Ruby】
症状
rails sでサーバーを起動し、コンソール上では起動してそうな感じだが、locakhost:3000にアクセスしても全く反応しない事象が発生しました。
以下はターミナル
ubuntu:~/environment/hello_app (master) $ rails s => Booting Puma => Rails 6.0.3 application starting in development => Run `rails server --help` for more startup options Puma starting in single mode... * Version 4.3.6 (ruby 2.6.3-p62), codename: Mysterious Traveller * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://127.0.0.1:8080 * Listening on tcp://[::1]:8080 Use Ctrl-C to stop実施したが解決しなかった対策
・rails server起動しなおし
・adBlockerなどは無効化
・ブラウザの開き直し
・bundle install→updateのやり直し
・rails s -b 0.0.0.0→rails s -b $IP -p $PORT解決方法
1.「preview」→「previewrunningapplication」で表示させた画面を閉じる
2.再度「previewrunningapplication」開きなおす
3.サーバーを起動下記画像の「Browser」の右側の■を押下→別タブで「Yay! You’re on Rails!」の画面が開けるようになりました。
以下は成功時のターミナル
ubuntu:~/environment/hello_app (master) $ rails s => Booting Puma => Rails 6.0.3 application starting in development => Run `rails server --help` for more startup options Puma starting in single mode... * Version 4.3.6 (ruby 2.6.3-p62), codename: Mysterious Traveller * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://127.0.0.1:8080 * Listening on tcp://[::1]:8080 Use Ctrl-C to stop Started GET "/" for 61.215.149.134 at 2021-03-04 14:18:46 +0000 Cannot render console from 61.215.149.134! Allowed networks: 127.0.0.0/127.255.255.255, ::1 (2.7ms) SELECT sqlite_version(*) Processing by Rails::WelcomeController#index as HTML Rendering /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/railties-6.0.3/lib/rails/templates/rails/welcome/index.html.erb Rendered /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/railties-6.0.3/lib/rails/templates/rails/welcome/index.html.erb (Duration: 13.0ms | Allocations: 479) Completed 200 OK in 64ms (Views: 27.3ms | ActiveRecord: 0.0ms | Allocations: 2758) Started GET "/" for 61.215.149.134 at 2021-03-04 14:18:58 +0000 Cannot render console from 61.215.149.134! Allowed networks: 127.0.0.0/127.255.255.255, ::1 Processing by Rails::WelcomeController#index as HTML Rendering /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/railties-6.0.3/lib/rails/templates/rails/welcome/index.html.erb Rendered /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/railties-6.0.3/lib/rails/templates/rails/welcome/index.html.erb (Duration: 7.9ms | Allocations: 192) Completed 200 OK in 12ms (Views: 10.8ms | ActiveRecord: 0.0ms | Allocations: 1165)