20210413のRubyに関する記事は12件です。

【Ruby】今夜放送予定のアニメ番組一覧を取得してGoogleカレンダーに書き込む

背景 自分はアニメが大好きなのですが、情報過多の著しい時代という事もあり、一体どの番組がどの曜日に放映されているのか見失ってしまいがちです。 テレビ備え付けの番組表も余計な情報が多く見辛いですし、わざわざ毎日チェックするほどマメかと言われればそうでもないため地味に困っていました。 何か良い方法は無いか探していたところ、どうやら「しょぼいカレンダー」というサイトがアニメに特化した番組情報を配信しているようです。 そしてありがたい事に、JSONやXMLという形で様々な情報を取得できるぽいので、今回は色々試してみました。 仕事の都合上、Googleカレンダーは毎日必ずチェックするので、この手順であれば気になるアニメを見逃さずに済みそうです。 使用技術 Google API AWS Lambda CloudWatch Events Ruby 2.5.1 下準備 まず、API経由でGoogleカレンダーにアクセスするために必要な準備がいくつかあるので、そちらから片付けていきます。 秘密鍵(JSON)を作成 ① Google Cloud Platformにログイン ② Google Calendar APIを有効化 ③ サービスアカウント(認証用)を作成 ④ 秘密鍵(JSON)をダウンロード この辺に関しては自分が過去に書いた記事(↓)に詳しく記載しているので、そちらをご覧ください。 参照: PHP/Laravelを使ってGoogleドライブにファイルをアップロードしてみる 記事内ではGooglドライブを使用していますが、基本的な流れは同じなのでそれぞれの単語を「Googleカレンダー」に適宜置き換えて操作していけば問題無いはず。 サービスアカウント作成後、秘密鍵などの情報が含まれたJSONファイルをダウンロードするところまで進めればOKです。 Googleカレンダーの設定 Googleカレンダーの右上にある歯車マークを押して「設定」へと進みます。 左サイドバー内の「マイカレンダーの設定」→「特定のユーザーとの共有」から、先ほど作成したサービスアカウントのメールアドレスを追加しましょう。 権限に関しては、「予定の変更」ができるようにしておきましょう。 あとは「カレンダーの統合」内にある「カレンダーID」をメモに控えておいてください。(後ほど使用します。) 実装 下準備ができたので、いよいよコードを書いていきます。 ディレクトリを作成 $ mkdir anime-google-calendar $ cd anime-google-calendar Gitを設定 $ git init $ touch .gitignore ./.gitignore .bundle .ruby-version /vendor/bundle credentials.json .env Gitで管理したくないものを記述。 Rubyのバージョンを指定 $ rbenv local 2.5.1 2.5系か2.7系を推奨。(Lambdaで運用する事を考慮して) 各種gemをインストール $ bundle init ↑のコマンドでGemfileを生成し、以下のように編集します。 ./Gemfile # frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem "google-api-client" gem "dotenv" その後、必要なgemをインストール。 $ bundle install --path vendor/bundle ※ 後ほどgemも含めてzipファイルにパッケージングする必要があるため、グローバルではなくローカル(「vendor/bundle」以下)にインストールします。 秘密鍵(JSON)をルートディレクトリ直下に配置 下準備のところで作成したサービスアカウントの秘密鍵(JSON)を「credentilas.json」にリネームし、ルートディレクトリ直下に配置します。 うっかりGitHubなどにアップしてしまうと大変な事になるので、ちゃんとGit管理から外れているか確認してください。 各種ファイルを作成 $ touch google_calendar.rb $ touch programs.rb $ touch main.rb $ touch .env ./google_calendar.rb require "bundler/setup" require "google/apis/calendar_v3" require "googleauth" require "dotenv" require "rexml/document" require "open-uri" Dotenv.load APPLICATION_NAME = "Google Calendar".freeze # そこまで重要ではないので適当な名前でOK CREDENTIALS_PATH = "./credentials.json".freeze # サービスアカウント作成時にDLしたJSONファイルをリネームしてルートディレクトリに配置 CALENDER_ID = ENV["CALENDER_ID"].freeze # Googleカレンダー設定ページの「カレンダーの統合」という項目内に記載されている値 class GoogleCalendar def initialize @service = Google::Apis::CalendarV3::CalendarService.new @service.client_options.application_name = APPLICATION_NAME @service.authorization = authorize @calendar_id = CALENDER_ID end # 認証 def authorize authorizer = Google::Auth::ServiceAccountCredentials.make_creds( json_key_io: File.open(CREDENTIALS_PATH), scope: Google::Apis::CalendarV3::AUTH_CALENDAR_EVENTS ) authorizer.fetch_access_token! authorizer end # カレンダーに予定(今回で言えば番組情報)を追加する def insert_event(summary, description, location, start_time, end_time) event = Google::Apis::CalendarV3::Event.new({ summary: summary, # タイトル description: description, # サブタイトル location: location, # チャンネル名 start: { date_time: start_time, # 開始時刻 time_zone: "Asia/Tokyo" }, end: { date_time: end_time, # 終了時刻 time_zone: "Asia/Tokyo" } }) @service.insert_event(@calendar_id, event) end end ./programs.rb require "rexml/document" require "open-uri" # しょぼいカレンダーから番組を取得する def get_programs(span) url = "http://cal.syoboi.jp/cal_chk.php?days=#{span}" # span: どれくらい先まで取得するかの期間を日にちで指定 xml = REXML::Document.new(open(url).read) programs = [] xml.elements.each("syobocal/ProgItems/ProgItem") { |item| programs << { title: item.attribute("Title").to_s, # タイトル sub_title: item.attribute("SubTitle").to_s, # サブタイトル st_time: Time.parse(item.attribute("StTime").to_s), # 開始時刻 ed_time: Time.parse(item.attribute("EdTime").to_s), # 終了時刻 ch_name: item.attribute("ChName").to_s, # チャンネル名 ch_id: item.attribute("ChID").to_s.to_i # チャンネルID } } programs.select{ |program| # データの取得期間を現在時刻から翌日の朝5時までに絞る st = Time.now day = Time.now.hour < 5 ? Date.today : Date.today + 1 ed = Time.new(day.year, day.month, day.day, 5) # 自分が住んでいる地域で放送されているチャンネルのIDを選択 # チャンネルIDの調べ方: http://cal.syoboi.jp/mng?Action=ShowChList ch_ids = [ 1, # NHK総合 2, # NHK Eテレ 3, # フジテレビ 4, # 日本テレビ 5, # TBS 6, # テレビ朝日 7, # テレビ東京 19, # TOKYO MX ] st < program[:st_time] and program[:st_time] < ed and ch_ids.include?(program[:ch_id]) }.sort_by{ |program| program[:st_time] } end ./main.rb require "./google_calendar.rb" require "./programs.rb" # 日時をISO 8601規格に整形する def format_time(time) ymd = time.to_s.split(" ")[0] hms = time.to_s.split(" ")[1] "#{ymd}T#{hms}+09:00" end # 番組を取得してGoogleカレンダーに書き込む programs = get_programs(1) google_calender = GoogleCalendar.new programs.each do |program| google_calender.insert_event( program[:title], program[:sub_title], program[:ch_name], format_time(program[:st_time]), format_time(program[:ed_time]) ) end ./env CALENDER_ID=自分のカレンダーID 実行 $ ruby main.rb 上手くいくとこんな感じでGoogleカレンダーにアニメ番組の情報が書き込まれるはずです。 AWS Lambda × CloudWatch Eventsで自動化 さすがに毎日手動で実行するのは厳しいので、AWS LambdaとCloudWatch Eventsを使って自動化しましょう。 Lambda関数用のファイルを作成 $ touch lambda_function.rb /lambda_function.rb require "./google_calendar.rb" require "./programs.rb" # 日時をISO 8601規格に整形する def format_time(time) ymd = time.to_s.split(" ")[0] hms = time.to_s.split(" ")[1] "#{ymd}T#{hms}+09:00" end def lambda_handler(event:, context:) # 番組を取得してGoogleカレンダーに書き込む programs = get_programs(1) google_calender = GoogleCalendar.new programs.each do |program| google_calender.insert_event( program[:title], program[:sub_title], program[:ch_name], format_time(program[:st_time]), format_time(program[:ed_time]) ) end end 基本的に「./main.rb」と中身は同じですが、実行部分を「lambda_handler」というメソッドで括っていたり、引数に「event:, context:」を渡していたりと微妙に違います。 参照: AWS Lambda関数ハンドラー Lambdaレイヤーを作成 Lambdaレイヤー: 複数のLambda関数で外部ライブラリやビジネスロジックを共有できる仕組み。 参照: AWS Lambda Layersでライブラリを共通化 今回はLambdaレイヤーを利用します。 各種gemをLambda関数と切り離しておく事でコードサイズを小さく保つ事ができますし、今後同じようなLambda関数を作る事になった際には使い回しができるので便利です。 gemをパッケージング(zipファイル) $ zip -r lambda_layers.zip ./vendor gemが入っている「vendor」以下をzipファイルに閉じ込めます。 上手くいくとこんな感じで「lambda_layers.zip」として出力されるはず。 AWSコンソール画面から「Lambda」→「レイヤー」→「レイヤーの作成」へと進み、先ほど出力したzipファイルをアップロードします。 Lambda関数を作成 Lambdaレイヤーの準備ができたので、次はLambda関数の作成に入ります。 AWSコンソール画面から「Lambda」→「関数」→「関数の作成」へと進み、必要な情報を入力してLambda関数を作成します。 $ zip -r codes.zip lambda_function.rb google_calendar.rb programs.rb credentials.json 先ほどと同じ容量で実行に必要なコード lambda_function.rb google_calendar.rb programs.rb credentials.json をzipファイル化。 Lambda関数の管理画面からzipファイルをアップロードします。 こんな感じで4つのファイルが反映されていれば成功です。 各種設定 あとは細かい設定がいくつかあるのでそれらを片付けていきます。 メモリ&タイムアウトを変更 この辺の数値はお好みで変えてください。 Lambdaレイヤーとの紐付け 「レイヤーの追加」から先ほど作成したレイヤーとバージョンを指定して追加。 環境変数をセット それぞれ適宜入力。 GEM_PATH /opt/vendor/bundle/ruby/2.5.0 TZ Asia/Tokyo ↑この2つは固定。 テスト実行 これで大体の設定は済んだので、実際に動くかどうかテストします。 テンプレート hello-world 名前 test パラメータ 空欄でOK 「Test」を実行して正常なレスポンスが返ってくればOKです。 スケジュール実行 最後に、決められたスケジュールで定期実行がされるようにします。 「設定」→「トリガー」→「トリガーを追加」をクリック。 トリガー名: CloudWatch Events ルール: 新規ルールの作成 ルール名: 任意 ルールの説明: 任意 ルールタイプ: スケジュール式 スケジュール式: 任意 cron式の書き方の説明については今回省略。 今回は毎朝午前9時に実行してもらう事を想定して「cron(0 0 * * ? *)」としました。(UTCだと日本時間と9時間ほど時差があるので注意。) 時間の経過を待ち、しっかりと定期実行されていれば成功です。 もし上手く行かなかった場合はCloudWatchのロググループ内にログが出力されているはずなので適宜デバッグしてください。 完成コード あとがき お疲れ様でした。もし記事通りに進めて上手く動作しない箇所があったらコメント蘭などで指摘していただけると幸いです。 快適なアニメライフを!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

herokuでマイグレートしようとしたらMysql2::Error: Table already existsが起きたときの解決策

起こったこと Railsでポートフォリオを作成中、デプロイした後にマイグレートしようとしたら、 Mysql2::Error: Table 'users' already exists が吐かれてマイグレートできなくなりました。 原因 原因ははっきりとしていて開発環境でテーブルを増やしてアソシエーションを組んだときに、マイグレーションファイルの作成順序が誤っていて(最初からちゃんとやっておけば良い話)マイグレートに失敗し、マイグレーションファイルの作成日時を手動で変更したことが原因でした。 試してみたこと まず、heroku run rails db:migrate:statusで本番環境のデータベースの状態を確認してみました。 予想通り、マイグレーションのステータスがおかしなことになっています。 本来、テーブルは4つありますが、下から2つ目のファイルは NO FILE となっています。 これが手動でマイグレーションファイルの作成日時を変更した弊害です。 この後、heroku run rails db:migrate:resetでデータベースのリセットを試みましたが、別のエラーが起こりうまくいきませんでした。 解決策 heroku run DISABLE_DATABASE_ENVIRONMENT_CHECK=1 rails db:drop db:create db:migrate を実行します。 このコマンドはデータベースを一度壊して作り直し、マイグレートするのを一度に実行するものです。 これでもう一度heroku run rails db:migrate:statusを実行し、ステータスを確認してみます。 ステータスが正常に戻りました。 他にも方法はあると思いますが私のケースではこのコマンド一発で解決できました。 エラーを通じて 本来、データベースをしょっちゅうresetしたりdropするということ自体、あまり好ましくないものだと思います。設計の甘さで今回のようなことになってしまったので、今後作成していく個人アプリケーションでは後々のことも考えてテーブル設計、実装順序の組み立てをしていこうという学びになりました。 同じようなエラーにあたった方の、助けになれれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

複数の配列を比較し、共通項の数を算出する

受け取った6つの数字を親配列に入れ、同様にN回6つの数字を受け取り子配列に入れます。 親配列と子配列を比較し、共通の数字の数を出力します。 ※便宜上、基準となる配列を親配列と名付け、比較する配列を子配列と名付けてます。 # 6つの数字を受け取り配列にする a, b, c, d, e, f = gets.split(' ').map(&:to_i) # 冗長になるので変数luc_numsに代入 luc_nums = a, b, c, d, e, f # 試行回数を変数Nに代入する N = gets.chomp.to_i # N回下記の処理をする N.times do |i| # 新たに6つの数字を受け取り配列にする a_l, b_l, c_l, d_l, e_l, f_l = gets.split(' ').map(&:to_i) # 冗長になるので変数lot_numsに代入 lot_nums = a_l, b_l, c_l, d_l, e_l, f_l # luc_numsとlot_numsを比較し、共通項を変数common_numsに代入 common_nums = luc_nums & lot_nums # lengthメソッドでcommon_numsの数を出力 puts common_nums.length end 他に簡潔な方法があると思いますが、Rubyの基本のみで解いてます。 最後にコメントアウト無しを載せます。 a, b, c, d, e, f = gets.split(' ').map(&:to_i) luc_nums = a, b, c, d, e, f N = gets.chomp.to_i N.times do |i| a_l, b_l, c_l, d_l, e_l, f_l = gets.split(' ').map(&:to_i) lot_nums = a_l, b_l, c_l, d_l, e_l, f_l common_nums = luc_nums & lot_nums puts common_nums.length end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

before_actionで同じコードをメソッドにまとめる方法

before_actionとは使用すると、コントローラで定義されたアクションが実行される前に、 共通の処理を行うことができるメソッド 修正前 def show @item = Item.find(params[:id]) end def edit @item = Item.find(params[:id]) unless @item.user_id == current_user.id redirect_to action: :index end end def update @item = Item.find(params[:id]) if @item.update(item_params) redirect_to item_path else render :edit end end showアクション、editアクション、updateアクションに同じ@item = Item.find(params[:id])が 連続して記述してある また、editアクションにあるunless @item.user_id == current_user.idをアプリの安全性を高めるため、updateアクションでも使えるようにしたい 修正後 before_action :set_item, only: [:edit, :update, :show] before_action :move_to_index, only: [:edit, :update] def show end def edit end def update if @item.update(item_params) redirect_to item_path else render :edit end end private def set_item @item = Item.find(params[:id]) end def move_to_index unless @item.user_id == current_user.id redirect_to action: :index end before_action :set_item, only:[:edit, :update, :show]と記述することでeditアクションと updateアクションとshowアクションとが実行される前に、set_itemに定義されている処理が実行される set_itemはプライベートメソッド内に記述 またunless @item.user_id == current_user.idも同じようにbefore_actionで記述して プライベートメソッド内でmove_to_indexと定義することでeditアクション、updateアクションで使用できる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Everyday Scripting with Ruby

!macOS-11.2.3 !ruby-2.7.2p137 hoge source ~/git_hub/grad_research_21s/members/nishitani/hoge.org
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails チュートリアル第二章

rakeコマンドとrailsコマンド Rails 4以前ではRakeを使っているため、 古いRailsアプリケーションを扱うためにはRakeについて学ぶ必要があります。 おそらくもっとも頻繁に使われていたRakeコマンドは、データベースのデータモデルを更新するためのrake db:migrateコマンドと、自動化されたテストスイートを実行するためのrake testコマンドの2つでしょう。 また、Rails 4以前のアプリケーションでは、rakeコマンドのバージョンをGemfileで定義しているため、Bundlerのbundler execコマンドを通して実行する必要があります。 したがって、例えばRails 5におけるマイグレーションコマンドは次のようになりますが、 $ rails db:migrate Rails 4以前では、次のように実行する必要があります。 $ bundle exec rake db:migrate Rakeの特徴 Rubyで記述ができる Rakefileというファイルに一連の処理を定義する (この処理のまとまりを「タスク」と呼ぶ) bundle exec ・bundle execをつけないと、Gemfile.lockに基づかずにgemのバージョンが決定される ・bundle execをつけると、Gemfile.lockに書かれているバージョンのgemが動く ・railsコマンドだけはbinを省略してもbin/railsと解釈される なので、railsコマンドは何も付けずに実行 ・binディレクトリにファイルが存在するコマンドはbin/◯◯ ・それ以外のコマンドはbundle exec ◯◯ 参考文献 bundle execって必要なの? https://qiita.com/d0ne1s/items/fa2dafcee02e963fe997 Railsのrakeってなんぞ? https://qiita.com/SuguruOoki/items/e736b15bbb80eacf66d7#dsldomain-specific-language%E3%81%A8%E3%81%AF RESTfulな設計 まずRESTについて簡単に解説しておきます。RESTとはアプリケーションの設計方法の1つで、操作の対象となるリソースをURLを使って表し、それに対してHTTPメソッドの「GET」「POST」「DELETE」「PUT」を使って操作を行なうというものです。あるURLへGETを使ってアクセスすれば、結果を取得でき、同じURLへPUTでアクセスすれば新しく作成されるよう設定を行ないます。 Railsのアプリケーションの場合、対象となるリソースはデータベースのテーブルがほとんどです。このリソースを表すURLが例えば「http://localhost:3000/sample」だったとします。RESTの考え方に従えば、この1つのURLに対してHTTPメソッドのGETでアクセスすればデータを取得し、PUTでアクセスすればデータの作成が行われるようにします。 Railsでは利用者からのリクエストで呼び出されるのはアクションですので、URLとHTTPメソッドの組み合わせ毎に呼び出されるアクションをルーティングとして設定すればいいことになります。 引用 rails rutes詳細 引用 Active Record 引用 https://railsguides.jp/active_record_basics.html resources resources :photos 上の記述により、アプリケーション内に以下の7つのルーティングが作成され、いずれもPhotosコントローラに対応付けられます。 引用
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

04/17(土)用 Windows10にDockerでRails6+MySQLの環境構築

環境構築の手順書です。 山岡楓が制作しています。 Railsのデフォルトホーム画面を表示させるところまでです。 環境 Windows10 Ruby2.7.1 Rails6.0.3.1 MySQL8.0 1.Docker for Windowsをインストール https://hub.docker.com/editions/community/docker-ce-desktop-windows/ 上記リンクにアクセスしてインストールする。 インストールが出来たら一度再起動して、Dockerを起動させてください。 2.必要ファイルの作成 どこまでもいいので任意の場所にディレクトリを作成する。デスクトップとか。 ここではRails_appというディレクトリを作成します。(名前はなんでもいい) で、VScodeでフォルダを開いてください。 (エディターはなんでもいいですが、VScodeがいいと思います。) インストールがまだの場合は以下リンクからインストール https://azure.microsoft.com/ja-jp/products/visual-studio-code/ Dockerfileの作成 VScodeでディレクトリを開けたらディレクトリ内にDockerfileを作成し、 以下のように記述します。 FROM ruby:2.7 RUN apt-get update -qq && apt-get install -y nodejs yarnpkg RUN ln -s /usr/bin/yarnpkg /usr/bin/yarn RUN mkdir /app WORKDIR /app COPY Gemfile /app/Gemfile COPY Gemfile.lock /app/Gemfile.lock RUN bundle install COPY . /app # Add a script to be executed every time the container starts. COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 # Start the main process. CMD ["rails", "server", "-b", "0.0.0.0"] Gemfileの作成 次に同じ階層にGemfileを作成し、以下のように記述します。 source 'https://rubygems.org' gem 'rails', '~>6' もう一つGemfile.lockというファイルを作成します。 中身は空でOKです。 entrypoint.shの作成 次にDockerfileの中でENTRYPOINTとして定義しているentrypoint.shの作成をします。 entrypoint.sh #!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /app/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@" docker-compose.ymlの作成 最後にdocker-compose.ymlというファイルを同階層に作成し、以下を記述してください。 docker-compose.yml version: '3' services: db: image: mysql:8.0 volumes: - ./tmp/db:/var/lib/mysql environment: - MYSQL_ALLOW_EMPTY_PASSWORD=1 web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/app ports: - "3000:3000" depends_on: - db MYSQL_ALLOW_EMPTY_PASSWORDを設定することでpasswordが空でもrootで接続できるようにしておきます。 3.Rails new / docker-compose buildを行う VScodeのターミナルを開き、作成したディレクトリの階層にいる事を確認したら、 以下を実行してください。 $ docker-compose run web bundle exec rails new . --force --database=mysql --forceは既存のファイルを上書き、--databaseでMySQLを指定しています。 実行完了したらビルドを行います。 $ docker-compose build 4、DBのホスト名の変更 / docker-compose upを行う 現状だとデータベースへの接続が出来ないので、 最初に作成したディレクトリ内に出来たconfig/database.ymlのhostの値をdbという値に置き換えてください。 ※全部コピペはしないでください。 database.yml default: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: host: db development: <<: *default database: app_development test: <<: *default database: app_test ここのdbがコンテナ名になります。 これが出来たら再度docker-compose buildを行ってからdocker-compose upを行います。 この状態でlocalhost:3000に接続するとおそらくエラーが出ます。 Webコンテナがmysql8.0のcaching_sha2_password認証方式に対応していない為です。 次の手順で認証方式をmysql_native_passwordに変更します。 5.MySQLの認証方式の変更 まずDBコンテナに入ります。 (VScodeでdocker-compose upを行っていると思うので、powershellを使って該当のディレクトリを開いてください。) そこでbashを起動します。 docker-compose exec db bash その後、mysqlコマンドで接続します。 mysql -u root 場所がmysql>になっている事を確認したら、 下記のクエリを実行してみてください。 mysql> select User,Host,plugin from mysql.user; すると以下のようなユーザ一覧と認証方式が出てきます。 +------------------+-----------+-----------------------+ | User | Host | plugin | +------------------+-----------+-----------------------+ | root | % | caching_sha2_password | | mysql.infoschema | localhost | caching_sha2_password | | mysql.session | localhost | caching_sha2_password | | mysql.sys | localhost | caching_sha2_password | | root | localhost | caching_sha2_password | +------------------+-----------+-----------------------+ 5 rows in set (0.00 sec) 上記で出てきたrootのpluginにあるcaching_sha2_passwordをmysql_native_passwordに変更します。 今回対象のroot@%のユーザ設定をALTER USERを使って変更します。 下記のクエリを実行してください。 ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY ''; 実行出来たら改めてselect User,Host,plugin from mysql.user;を実行すると、 +------------------+-----------+-----------------------+ | User | Host | plugin | +------------------+-----------+-----------------------+ | root | % | mysql_native_password | | mysql.infoschema | localhost | caching_sha2_password | | mysql.session | localhost | caching_sha2_password | | mysql.sys | localhost | caching_sha2_password | | root | localhost | caching_sha2_password | +------------------+-----------+-----------------------+ 5 rows in set (0.00 sec) 上記のような表示に変わっていると思います。 その後exitを2回実行して、元いた階層に戻ります。 で、階層が作成したディレクトリなのを確認出来たら以下を実行します。 $ docker-compose exec web bundle exec rails db:prepare 6.Railsのホーム画面にアクセス DBを作成し、localhost:3000すると、Railsのホーム画面にアクセスできるようになります。 環境構築は以上です。 お疲れ様でした。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Fakersでダミーの注文データを作る(Ruby on Rails、seeds)

やりたいこと 社内研修で、ウォーターサーバーの替えのボトルの在庫を見張って自動発注するというプチIoTシステムをRailsで開発しています。 Railsで注文データのダミーを作りたかったのですが、調べても意外にそれっぽいハウツーに出会えなかったのでメモ。(自分のぐぐり力不足?) ちなみに、注文モデル(order.rb)には以下のカラムがあります。 カラム データ型 delivery_date date 納品日 qty integer 注文数 order_date date 注文した日付 status integer 0: 注文済み、1: 納品済み created_at datetime updated_at datetime item_id bigint 注文アイテムの外部キー user_id bigint 注文したユーザーの外部キー ちなみに、環境。 Rails6.0.3, macOS Catalina 10.15.7 実装! 今回はFakerというgemを活用して、ダミーの注文データを30個生成します。 (gemとは?=>Railsをさらに便利にしてくれるライブラリ(=拡張機能=オプション機能=カスタマイズ機能)みたいなやつ) Fakerそのもののインストールの仕方に関してはネットにたくさん記事があるので割愛します。 seeds.rb # 最新の注文は個別で作る order_date = Faker::Date.between(from: 5.days.ago, to: Date.today) Order.create( order_date: order_date, qty: Faker::Number.between(from: 1, to: 10), delivery_date: order_date + 3, # 納品日は注文日の3日後としておく。下に詳述。 status: 0, user_id: 2, item_id: 1) # 残り29個はランダムで生成してもらう 29.times do order_date = Faker::Date.between(from: 1.year.ago, to: 10.days.ago) Order.create( order_date: order_date, qty: Faker::Number.between(from: 1, to: 10), delivery_date: order_date + 3, status: 1, user_id: Faker::Number.between(from: 1, to: 2), item_id: Faker::Number.between(from: 1, to: 3) ) end ↑コードを見れば何をしたいか大体わかると思うので、解説は加えません。 これ以外の実装がしたい場合は、Fakerはその辺のチュートリアル記事よりも、本家に当たるのが吉でしたのでこちらを参照すると良いと思います。 Fakerドキュメント https://github.com/faker-ruby/faker#faker Faker::Date https://github.com/faker-ruby/faker/blob/master/doc/default/date.md#fakerdate ちなみに、当初、delivery_dateもFakerしようと思ったのですが、そうすると、delivery_date(納期)がorder_date(注文日)よりも前になってしまうなど整合性が取れないデータが発生することを懸念し、order_date + 3 という計算にしました。 余談ながら、この計算方法(文法)で合っているかは、ターミナルでrails db:seedする前に、rails consoleで確認できることを発見!↓ % rails console Running via Spring preloader in process 48085 Loading development environment (Rails 6.0.3) irb(main):001:0> order_date = Faker::Date.between(from: 1.year.ago, to: 10.days.ago) => Tue, 09 Jun 2020 irb(main):002:0> order_date + 15 => Wed, 24 Jun 2020 irb(main):003:0> 生成! ターミナルでrails db:seedすると下記のようにデータが生成されました!↓ 最後に 何かご指摘やもっといい方法などあればぜひ教えてください。 ありがとうございます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

each_with_indexとeach.with_index(数字)で繰り返し処理

0. はじめに rubyには、「繰り返し処理」というものが存在します。具体的なメソッドで言うと、eachやtimes, whileなどです。 この記事では、私がこれまでによく利用していたeachメソッドの、少し応用系である2つのメソッドを記していきます。あと追加で、私が開発中に作って没になった「好きな場所で繰り返しを止める」メソッドを紹介します。また、具体例では以下の配列を扱います。 array = ["一郎", "二郎", "三郎"] 1. each_with_index 【Ruby each_with_indexの書き方】 配列名.each_with_index do |item, i| # 処理 end array.each_with_index do |item, i| puts "#{item}は#{i}番目です" end => 一郎は0番目です 二郎は1番目です 三郎は2番目です each_with_indexは、配列の要素地震が何番目なのか、そしてその番号(index)も処理で同時に扱うことができるメソッドです。indexを扱うことができる繰り返し処理で、処理内で一意性を与えたい時に特に用いていました。 しかし、配列のindexは0から始まります。数え初めが1である人間にとっては、少し馴染みにくい印象があります。そこで便利なのが、次のeach.with_index(数字)メソッドです。 2. each.with_index(数字) 【Ruby each.with_indexの書き方】 配列名.each.with_index(開始させたい値) do |item, i| # 処理 end array.each_with_index(1) do |item, i| puts "#{item}は#{i}番目です" end => 一郎は1番目です 二郎は2番目です 三郎は3番目です each.with_index(数字)では、始まりの数字を指定できます。(上記例では始まりが"1") このメソッドは自由度があり、意味もいくらか伝わりやすい気もします。使い分けが重要ですね。 3. 好きな場所で繰り返しを止める 具体例から示します。 例えば、5つの要素を持つ配列が存在するとします。この配列を2回繰り返し、3回目は要素の2つ目で繰り返しを止めたい、といった場合に使います(笑)。以下は例です。 配列:①②③④⑤ ①②③④⑤ | ①②③④⑤ | ①② ↑ココマデ! この場面で用いるのが、2個のtimesメソッドと、繰返しを中断させるbreakです。 3.times do |i| 5.times do |ii| puts "#{ii}" break if i==2 && ii==1 end end # 実際の結果 3.times do |i| 5.times do |ii| puts "#{ii}" break if i==2 && ii==1 end end => 0 1 2 3 4 0 1 2 3 4 0 1 timesメソッドは、指定回数繰り返しを行います。 breakメソッドは、条件分岐により途中で繰返しを中断します。 これらを組み合わせて「好きな場所で繰り返しを止める」メソッドを開発しました。 まぁ、こんなメソッドいつ使うんだよ、って感じですけどね(笑)。 4. さいごに 最後までお読みいただきありがとうございました。 今回の記事のようにこれまで学習した内容について、徐々にアウトプットしていきたい。 自分がアウトプットした内容が誰かの役に立てば幸いです。 余談ですが... 今回この記事で扱った「繰り返し処理」というのは、プログラミング技術発達の過程で生まれた「構造化プログラミング」から優位的に導入されました。プログラムを遂行する三大ロジックのうちの一つです。 「繰り返し処理」の他には、「順次進行」「条件分岐」があります。 「順次進行」は命令を上から実行するロジック、基本的なロジックですね。 「条件分岐」は何らかの判定を行い、その結果で次の命令を決めるロジック。rubyだとif文とかですね。 命令を実行するロジックという切り口では、「繰り返し処理」は中々重要な立ち位置にあるそうですね。 参考 「オブジェクト指向でなぜつくるのか」/ 平澤 章 / 日経BP社 / 2004年6月初版
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

deviseを使用したuserモデルバリデーションチェックのRSpec初コーディング(前編)

時間をかけ調べました。 間違っている箇所があればご指摘お願いします。 自分がかけたバリデーションチェックのRSpecをコーディングを公開します。 views/devise/registrations/new.thml.erb すべて必須なので全項目に---presence: true のバリデーションがかかっています。 このテストコードを難しいと感じる点 Q1.生年月日欄 どのようにテストコードを書けば良いのか? 数字3つ用意して、「年」「月」「日」を間に挟むのか??? +とかでつなぐのか? valid?というコードの目的を知るとわかる Q2.最低字数6文字で英数混合のパスワードを作る ググっても6文字で英数混合パスワードというものは出てこない。うーーっ Fakerのパスワードで実現するのはムズカしいと知ることから始まる Q3.エラーメッセージはどんなメッセージを書くのか? これから作るアプリ完成時の表示を想像しないといけない。これまた難問。ググるか? 何に対してテストするかを考える。 テストの仕様 バリデーションチェック「正常系」 ①全ての項目がきちんと条件通り揃ってusersテーブルに「保存」される →全て必須なので1通りのみ バリデーションチェック「異常系」 ②1つの項目が欠けた場合を想定して、全ての項目で欠けたパターンのusersテーブルに「保存」されないチェック →全ての項目なのでニックネーム・メールアドレス・パスワード・パスワード(確認)・お名前(姓)・お名前(名)・お名前(カナ・姓)・お名前(カナ・名)・生年月日の9通り 実装に向けての準備 Gemfile group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] gem 'rspec-rails', '~> 4.0.0' gem 'factory_bot_rails' gem 'faker' gem 'gimei' end RSpec:Ruby専用テスティングフレームワーク faker:テスト用のダミーデータをランダムに生成するGem gimei:日本語対応のテスト用のダミーデータをランダムに生成するGem 以上をインストールしました。 terminal bundle install 次に terminal rails g rspec:install さらに fakerの作成ダミーデータの仕様決定のためのファイルを用意します。 specフォルダの下にfactoriesというフォルダと「テーブル名(複数形名)」.rbのフォルダを作って用意します。 ダミーデータの仕様を決める設計図 以下になります。 spec/factories/users.rb FactoryBot.define do #1.ニックネーム nickname {Faker::Name.last_name} #2.メールアドレス email {Faker::Internet.free_email} #3.パスワード password {Faker::Lorem.characters(number: 10, min_alpha: 4, min_numeric: 1) } #4.確認用パスワード password_confirmation {password} #5.漢字(姓) first_name { Gimei.first.kanji } #6.漢字(名) last_name { Gimei.last.kanji } #7.漢字(姓・カナ)#pronoはpronounceでカナとしています。 first_name_prono { Gimei.first.katakana } #8.漢字(名・カナ)#pronoはpronounceでカナとしています。 last_name_prono { Gimei.last.katakana } #9.生年月日 birthday { Faker::Date.backward } end end Gimei.first.kanjiはGimeiGemのおかげで漢字が入ります。 Gimei.last.kanjiも同様に漢字が入ります。 Gimei.first.katakanaはGimeiGemのおかげでカタカナが入ります。 Gimei.last.katakanaはGimeiGemのおかげでカタカナが入ります。 password {Faker::Lorem.characters(number: 10, min_alpha: 4, min_numeric: 1) } については6文字で英数混合の設定にとても苦労したので、別で詳しく解説しています。よければリンク先でご確認いただければと思います。 Fakerを使って英数混合のパスワードを作る(Q2の解答にもなっております) ↓↓↓ https://qiita.com/takuo_maeda/items/70a3fb2cc190099f3a5e はじめて書いたテストコード user_spec.rb require 'rails_helper' RSpec.describe User, type: :model do describe '#create' do before do #インスタンス変数で渡す:理由 describe,itの変数のスコープが違うので #:userはハッシュのシンボルになっていてspec/factories/users.rbで作った設計図通りの ランダムなダミーデータを作ってくれます。 @user = FactoryBot.build(:user) end #正常系 it 'nickame,email,password,password_confirmation,first_name,last_name,first_name_prono,last_name_prono,birthdayの値が存在すれば登録できること' do expect(@user).to be_valid end        #異常系1 it 'nicknameが空では登録できないこと' do @user.nickname = '' @user.valid? expect(@user.errors.full_messages).to include("Nickname can't be blank") end    #異常系2 it 'emailが空では登録できないこと' do @user.email = '' @user.valid? expect(@user.errors.full_messages).to include("Email can't be blank") end    #異常系3 it 'paswordが空では登録できないこと' do @user.password = '' @user.valid? expect(@user.errors.full_messages).to include("Password can't be blank") end    #異常系4 it 'pasword_confirmationが空では登録できないこと' do @user.password_confirmation = '' @user.valid? expect(@user.errors.full_messages).to include("Password confirmation can't be blank") end    #異常系5 it 'first_nameが空では登録できないこと' do @user.first_name = '' @user.valid? expect(@user.errors.full_messages).to include("First name can't be blank") end    #異常系6 it 'last_nameが空では登録できないこと' do @user.last_name = '' @user.valid? expect(@user.errors.full_messages).to include("Last name can't be blank") end    #異常系7 it 'first_name_pronoが空では登録できないこと' do @user.first_name_prono = '' @user.valid? expect(@user.errors.full_messages).to include("First name prono can't be blank") end    #異常系8 it 'last_name_pronoが空では登録できないこと' do @user.last_name_prono = '' @user.valid? expect(@user.errors.full_messages).to include("Last name prono can't be blank") end    #異常系9 it 'birthdayが空では登録できないこと' do @user.birthday = '' @user.valid? expect(@user.errors.full_messages).to include("Birthday can't be blank") end end end 参考 ・valid?メソッドは以下の2つの役割 *Q3の「エラーメッセージはどんなメッセージを書くのか?」の解答にもなっています。 ①true/falseを返す true false 作成したデータが正しく保存される場合 保存されない場合はfalse ②保存されない場合は「なぜ保存されないのか」のエラーメッセージを生成する Q3の解答 ・バリデーション presence: trueは空欄では保存できない ・それに対応するエラーメッセージは「〜can't be blank」(=空欄で保存はできない) 参考:エラーメッセージ集 ②の理由からincludeの後に表示されるメッセージを書くことになっています。テストを作った後にbinding.pryしてコンソールでどんなメッセージが表示されるのかを取得しておきました。ご参考まで。 取得コマンド @user.errors.full_messages エラーメッセージはモデルのerrorsに格納されています。 *userモデルなので @userになっています。 [1] pry(#<RSpec::ExampleGroups::User::Nested>)> user.errors.full_messages => ["Email can't be blank", "Email can't be blank", "Password can't be blank", "Nickname can't be blank", "Encrypted password can't be blank", "First name can't be blank", "Last name can't be blank", "First name prono can't be blank", "Last name prono can't be blank"] 基本:userモデルバリデーションで「presence: ture」とした場合のエラーメッセージは 「〜can't be blank」が共通部分となります。 ’保存先カラム名を元に、〜部分の主語を変え、先頭を大文字にし、スネークケースで命名されていれば_(アンダーバー)をはずして主語を書いてください。’ Q2の解答 姓年月日がどんなview画面で構成されているかはいっさい関係いなく データベースに保存されるかされないかをテストしているので保存されているデータの形式を確認してテストコードを書けば良い。 データベースには以下のようにデータが保存されているので rails fakerにFaker::Date.backward こうすると テストが成功すると「2019-01-03」のようなダミーデータを作ってくれます。 terminal nickame,email,password,password_comfirmation,first_name,last_name,first_name_prono, last_name_prono,birthdayの値が存在すれば登録できること nicknameが空では登録できないこと emailが空では登録できないこと paswordが空では登録できないこと pasword_confirmationが空では登録できないこと first_nameが空では登録できないこと last_nameが空では登録できないこと first_name_pronoが空では登録できないこと last_name_pronoが空では登録できないこと birthdayが空では登録できないこと ひとまず段落です。 参考 単体テストコードRspecでの.validか.valid?か(初期の私の記事です。よかったら) https://qiita.com/takuo_maeda/items/8755d38ca24172225250
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyの:(コロン)の種類

Rubyの:(コロン)の種類について Rubyを学習していて当たり前のように使っていた:(コロン)。 たくさんの種類がある事を知らずに何となくで使用していました。 基本を押さえておけば、後々の学習にも役立つと思いまとめました。 この記事はUdemyの以下の講座を参考にまとめさせて頂きました。 無料で公開されているなので、気になる方は是非。 Udemy :(コロン)の種類 シンボル qiita.rb :asdf 文字列の先頭にコロンがある場合は、シンボルのコロン。 シンボルは文字列の上位版。 シンボルは重複を許さず、シンボルの中身が同じであれば、コンピュータ内部では1箇所に保存され、処理速度がちょっと速くなったりするらしい。 文字列の場合は、中身が同じであってもコンピュータ内部では、別々の保存領域が必要。 シンボルをキーにしたハッシュの定義 まずはハッシュがどういうものかというと、 qiita.rb hash1 = { キー1 => バリュー1, キー2 => バリュー2, キー3 => バリュー3 } キーとバリューがセットになっているのがハッシュ。 ちなみに配列は重複した値を[]内に入れる事ができますが、ハッシュは重複(duplicated)したキーで違う値を定義することはできません。 ここまでの内容でわかったことは、 ・ハッシュのキーはキー同士の重複を許さない ・シンボルもコンピュータの内部上、重複を許さない この重複を許さない性質をもつハッシュとシンボルは非常に相性がいい。 そしてハッシュは次のように定義ができる。 qiita.rb hash1 = { "key1" => "value1", "key2" => "value2", "key3" => "value3" } hash2 = { :key => "value" } /これが正式な書き方/ hash2 = { key: "value" } /これはシンボルをキーにした省略系の書き方/ つまり、{}の中の後ろコロンは、シンボルをキーにしたハッシュのコロンというわけです。 メソッドのキーワード引数 メソッドとは、よく自動販売機に例えられます。 自動販売機は、お金を入れてボタンを押すとジュースが出てきます。 メソッドは、引数を渡してメソッドを実行すると戻り値が返ってきます。 何かを入れて、何を実行すると、何かが返ってくるものがメソッド。 キーワード引数を使用したメソッドは以下のようになります。 qiita.rb /普通の引数とメソッド/ def add(hikisu1, hikisu2) kotae = hikisu1 + hikisu2 return kotae end puts add(3, 4) /キーワード引数とメソッド/ def add(hikisu1:, hikisu2:) kotae = hikisu1 + hikisu2 return kotae end puts add(hikisu: 3, hikisu2: 4) そしてキーワード引数には以下の特徴があります。 引数に見出し(タイトルをつける事ができる) キーワード引数は順番が入れ替わってもOK 通常の引数と一緒に使える メソッドの呼び出しは括弧を省略できる railsを使っているとこんなコードをよく見かけます。 qiita.rb render ("users/user", user: @user) /()は省略可/ 1つ目は普通の引数ですが、user: @userの部分はキーワード引数です。 メソッドを呼び出す時の引数として後ろコロンが使われていたら、それはキーワード引数。 よく見るコードの解説 ここまで理解ができればきっと次のコロンもどんなものかがわかるはず。 qiita.rb validates :content, presence: true, length: { maximum: 50 } validatesというメソッドがあって、それに対する引数が記述されています。 以下がvalidatesメソッドに渡されている引数たち。 qiita.rb :content →先頭にコロンがあるのでシンボル! presence: true →validatesメソッドの引数部分で後ろコロンなのでキーワード引数 presence:というキーワード引数は、trueですよって渡してる(presenceという見出しをつけてる) length: →validatesメソッドの引数部分で後ろコロンなのでキーワード引数 { maximum: 50 } →波括弧があるからこれはハッシュ ハッシュの内側で後ろコロンは、シンボルをキーにしたハッシュ。 続いてこちら qiita.rb <%= link_to '削除する', @post, method: :delete, data: { confirm: '本当に削除してOK?'} %> 細かく分解していくと、 qiita.rb link_to →これがまずメソッドになります。そしてlink_to以下はメソッドに与えられた引数。 '削除する' @post →それぞれ第一引数、第二引数。 method: →キーワード引数 :delete →シンボル :data →キーワード引数 { confirm: '本当に削除してOK?'} →波括弧はハッシュ。 ハッシュの内側に後ろコロンがあるから、シンボルをキーにしたハッシュ。 まとめ 先頭にコロンがあったらシンボル ハッシュの内側に後ろコロンが会ったら、シンボルをキーにしたハッシュ メソッドの引数部分に後ろコロンがあったら、メソッドのキーワード引数
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails6】ゲストユーザー機能の実装例

エンジニア転職でポートフォリオとなるアプリを開発する際、企業の方がアプリを確認しやすいようにユーザー登録しなくてもよいゲストユーザー機能の実装が望ましいという話をよく聞きます。 そこで今回は、ぼくが自分のポートフォリオで実装したゲストユーザー機能の例を紹介します。 前提 deviseを使ってユーザー登録機能が実装されている Rails 6.0.3.5 動作確認 「ゲストユーザーとしてログインする」ボタンをクリックすると、ゲストユーザーでアプリにログインできます。 ルーティングの設定 以下のようにパスとアクションを組み合わせを定義します guest_log_inアクションはこのあとコントローラーで自作で定義するアクションです。 config/routes.rb devise_scope :user do post 'users/guest_log_in', to: 'users/sessions#guest_log_in' end アクションの定義 以下のようにguest_log_inアクションを定義します。 app/controllers/users/sessions_controller.rb class Users::SessionsController < Devise::SessionsController def guest_log_in user = User.guest sign_in user redirect_to calendars_path, notice: 'Logged in as a guest' end end アクションの中で使用しているsign_inメソッドは、deviseが提供する機能で、サインイン状態にします。 ちなみにdeviseの公式GitHubのdevise/app/controllers/devise/sessions_controller.rb内のDevise::SessionsController#createがsign_inメソッドです。※調べたことを書いているのですが、もし間違っていたらご指摘ください。ソースコードはこちら。 またguestメソッドはこのあとモデルで定義します。 モデルの定義 モデルにはクラスメソッドであるguestメソッドを定義します。 app/models/user.rb class User < ApplicationRecord def self.guest find_or_create_by!(email: 'guest@guest.mail') do |user| user.name = 'ゲストユーザー' user.password = SecureRandom.urlsafe_base64 end end end find_or_create_by!メソッドはRailsに用意されているメソッドで、「条件を指定して初めの1件を取得し、1件もなければ作成」します。 この場合、emailカラムがguest@guest.mailである初めのレコードを取得し、なければnameカラムが「ゲストユーザー」、passwordはランダムな値を生成してくれるRubyモジュールであるSecureRandomを使って生成し、新しいレコードを作成します。 ぼくのアプリではusersテーブルにnameカラムがあるため、nameの値を指定しています。 SecureRandomについては以下を参考にしてください。 Ruby 3.0.0 リファレンスマニュアル SecureRandomモジュール リンクボタンの作成 リンクボタンは任意の場所に以下のように記述します。 <%= link_to "Log in as a guest", users_guest_log_in_path, method: :post %> ゲストユーザーの編集ができないようにするアクション ポートフォリオを公開した時にゲストユーザーを編集可能な状態していると、イタズラを受けてしまう危険性もあるので、編集できないようにするアクションを定義します。 app/controllers/users/registrations_controller.rb before_action :ensure_normal_user, only: :edit def ensure_normal_user if resource.email == 'guest@guest.mail' redirect_to user_path(@user.id), notice: 'You can not edit guest user' end end 参考資料 deviseの公式GitHub Railsドキュメント find_or_create_by Ruby 3.0.0 リファレンスマニュアル SecureRandomモジュール 【Qiita】簡単ログイン・ゲストログイン機能の実装方法(ポートフォリオ用)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む