- 投稿日:2020-07-12T23:40:05+09:00
[Ruby on Rails] date_selectで分割されたパラメーターをFormObjectに渡す。
一つのフォームの送信で複数のモデルを更新したいときなど、FormObjectを利用するかと思います。
(FormObjectについて知りたい方はこちら:【Rails】FormObjectを使ってほしい)そのFormObjectに
date_select
で分割されたパラメーターをそのまま渡すことできなく困っていた矢先、FormObjectにinclude ActiveRecord::AttributeAssignment
を宣言したら、まとめて渡すことができたので以下にて紹介したいと思います。説明
画像の
CreateUser
ボタンを押すと、コントローラーのcreateアクションが走ります。user_paramsメソッドでStrongParameterの仕組みを使って、分割されたパラメーター<ActionController::Parameters {"birthday(1i)"=>"2020", "birthday(2i)"=>"7", "birthday(3i)"=>"13"} permitted: true>
を取得。app/controllers/users_controller.rbclass UsersController < ApplicationController before_action :set_user, only: [:show, :edit, :update, :destroy] # GET /users # GET /users.json def index @users = User.all end # GET /users/1 # GET /users/1.json def show end # GET /users/new def new @user = Form.new end # GET /users/1/edit def edit end # POST /users # POST /users.json def create @user = Form.new(user_params) respond_to do |format| if @user.save format.html { redirect_to @user, notice: 'User was successfully created.' } format.json { render :show, status: :created, location: @user } else format.html { render :new } format.json { render json: @user.errors, status: :unprocessable_entity } end end end # PATCH/PUT /users/1 # PATCH/PUT /users/1.json def update respond_to do |format| if @user.update(user_params) format.html { redirect_to @user, notice: 'User was successfully updated.' } format.json { render :show, status: :ok, location: @user } else format.html { render :edit } format.json { render json: @user.errors, status: :unprocessable_entity } end end end # DELETE /users/1 # DELETE /users/1.json def destroy @user.destroy respond_to do |format| format.html { redirect_to users_url, notice: 'User was successfully destroyed.' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_user @user = User.find(params[:id]) end # Only allow a list of trusted parameters through. def user_params params.require(:user).permit(:birthday) end endapp/forms/form.rbclass Form include ActiveModel::Model include ActiveModel::Attributes attribute :birthday, :date def to_model User.new(birthday: birthday) end def save return false if invalid? to_model.save end end
@user = Form.new(user_params)
で記載されているように、
分割されたパラメーターをFormObjectの初期値で渡そうと思うと、分割されているが故にエラーが起きてしまう。<ActionController::Parameters {"birthday(1i)"=>"2020", "birthday(2i)"=>"7", "birthday(3i)"=>"12"} permitted: true>
をまとめてbirthdayパラメーターとして渡したい。
そこで、
include ActiveRecord::AttributeAssignment
を宣言すると、分割されたパラメーターをまとめてattributeに渡してくれる。一件落着。app/forms/form.rbclass Form include ActiveModel::Model include ActiveModel::Attributes # 追加 include ActiveRecord::AttributeAssignment attribute :birthday, :date def to_model User.new(birthday: birthday) end def save return false if invalid? to_model.save end end
- 投稿日:2020-07-12T23:40:05+09:00
[Ruby on Rails] date_selectで分割されたパラメーターFormObjectに渡す。
一つのフォームの送信で複数のモデルを更新したいときにFormObjectを利用するかと思います。
(FormObjectについて知りたい方はこちら:【Rails】FormObjectを使ってほしい)そのFormObjectに
date_select
で分割されたパラメーターをそのまま渡すことできなく困っていた矢先、FormObjectにinclude ActiveRecord::AttributeAssignment
を宣言したらいい感じにまとめて渡せたので以下にて紹介したいと思います。説明
画像の
CreateUser
ボタンを押すと、コントローラーのcreateアクションが走り、StrongParameterを取得するuser_params
メソッドにて、分割されたパラメーター<ActionController::Parameters {"birthday(1i)"=>"2020", "birthday(2i)"=>"7", "birthday(3i)"=>"12"} permitted: true>
が取得される。app/controllers/users_controller.rbclass UsersController < ApplicationController before_action :set_user, only: [:show, :edit, :update, :destroy] # GET /users # GET /users.json def index @users = User.all end # GET /users/1 # GET /users/1.json def show end # GET /users/new def new @user = Form.new end # GET /users/1/edit def edit end # POST /users # POST /users.json def create @user = Form.new(user_params) respond_to do |format| if @user.save format.html { redirect_to @user, notice: 'User was successfully created.' } format.json { render :show, status: :created, location: @user } else format.html { render :new } format.json { render json: @user.errors, status: :unprocessable_entity } end end end # PATCH/PUT /users/1 # PATCH/PUT /users/1.json def update respond_to do |format| if @user.update(user_params) format.html { redirect_to @user, notice: 'User was successfully updated.' } format.json { render :show, status: :ok, location: @user } else format.html { render :edit } format.json { render json: @user.errors, status: :unprocessable_entity } end end end # DELETE /users/1 # DELETE /users/1.json def destroy @user.destroy respond_to do |format| format.html { redirect_to users_url, notice: 'User was successfully destroyed.' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_user @user = User.find(params[:id]) end # Only allow a list of trusted parameters through. def user_params params.require(:user).permit(:birthday) end endapp/forms/form.rbclass Form include ActiveModel::Model include ActiveModel::Attributes attribute :birthday, :date def to_model User.new(birthday: birthday) end def save return false if invalid? to_model.save end end
@user = Form.new(user_params)
で記載されているように、
分割されたパラメーターをFormObjectの初期値で渡そうと思うと、分割されているが故にエラーが起きてしまう。<ActionController::Parameters {"birthday(1i)"=>"2020", "birthday(2i)"=>"7", "birthday(3i)"=>"12"} permitted: true>
をまとめてbirthdayパラメーターとして渡したい。
そこで、
include ActiveRecord::AttributeAssignment
を宣言すると、分割されたパラメーターを一旦まとめて渡してくれる。一見落着。app/forms/form.rbclass Form include ActiveModel::Model include ActiveModel::Attributes # 追加 include ActiveRecord::AttributeAssignment attribute :birthday, :date def to_model User.new(birthday: birthday) end def save return false if invalid? to_model.save end end
- 投稿日:2020-07-12T23:31:07+09:00
TECH CAMP学習 個人アプリ④git ignore
5Kって何だ!?
個人アプリを作成中に、Git Hubにプルリクエストをしてない事をユーザー管理作成中の時に気づいてしまいました。
かなりデータが溜まっていって、ワークスペースになんか『5K』と表示されてしまい全てをプルリクエストできません、と注意で書かれてありました。Git Hubデスクトップにもchangesが500と表示されてしまい500!!??となりました。5Kの意味はこれの事なのかな??
そしてやはり遅かったのか、どうすればプルリクエストできるのか、ファイルのどれかを消さないといけないのかと思い何となく一部のファイルを消去。
ただそれだけでは全くうまくいかず、少なくならず、ひたすらにググってググって、やっと見つけたのがgit ignore!!!
VS code 内で.git ignoreファイルを作り、以下のコードを入れました。
これで一気にchangesが91まで縮小され、プルリクエストができるようになりました。
- 投稿日:2020-07-12T22:37:42+09:00
【ターミナル】ターミナル強制終了後、rails sしたらA server is already runningとなった時の対処法【Rails】
「controll+z」でサーバーを切ったら、rails sが効かなくなった。どうしよう・・という問題。
【バージョン】
Rails 5.2.4.3
Ruby 2.5.1
macOS Catalina 10.15.4
【経緯】
ローカルで作業中、rails sで立ち上げたlocalhost:3000を「controllキー + c」でサーバを切り、再度「rails s」で起動させようとしたらうんともすんともいわず・・・ほかの切り方がないか調べたところ「controllキー + z」で強制終了できることが判明。
しかし、「controllキー + z」で強制終了後、
再度、rails sで立ち上げたところ、localhost:3000が立ち上がらず、下記のエラーが出た。ターミナルA server is already running. Check /Users/名前/XXX/XXXXXX/tmp/pids/server.pid.「サーバーはすでに起動しています」と言われた。
【解決方法】
なので、
ターミナル% lsof -i:3000でどんなプロセスが動いているのかを確認してみる。
lsofコマンド
…「LiSt Open Files」(開いているファイル群を列挙する)という言葉に由来するようです。その名の通り、「プロセスが開いているファイル」を表示するコマンドらしいです。
ターミナルCOMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME ruby 6792 satokokinoshita 11u IPv4 0x95b341c28591923f 0t0 TCP localhost:hbci (LISTEN) ruby 6792 satokokinoshita 22u IPv6 0x95b341c27813ce6f 0t0 TCP localhost:hbci (LISTEN)ここで、COMMANDがrubyとなっているPIDの番号 (ここだと6792、下の画面だと3405) を、○に入れる。
ターミナル% kill -9 〇〇〇〇
これで、rails sで再度起動できるようになりました。※ちなみにkillコマンドについてはkill -9(強制終了)以外は試しておりません。
localhost:3000のサーバーを切った時に消えるはずのプロセスが、強制終了したことで一部残ってしまっていたのだと思います。
「PID(プロセスアイディー)」は、プロセスを識別するための一意の数字になりますが、
PIDを指定してkillコマンドで切ることで、サーバーを切った状態に戻ったということになったのだと思います。【参考にさせていただいたサイト】
このポートで実行中のプロセスはどれ? lsofコマンドの使い方
プロセスを止める最終手段killコマンドの種類・シグナルの使い方
【当該エラーに直面した時の感想的な】
Railsの画面に表示されるエラーは慣れたけど、ターミナルのエラーってあまりないので変な汗がでました?
ターミナルコマンドはどんどん慣れていきたいです。
- 投稿日:2020-07-12T21:13:46+09:00
Digdag公式ドキュメントからDigdagを学ぶ-Scheduling workflow
目標
Digdagの公式サイトのドキュメントのScheduling workflowの翻訳+α
DigdagのRubyを使ってRailsにバッチを作るまでが最後の目標
http://docs.digdag.io/scheduling_workflow.html目次
Getting started
Architecture
Concepts
Workflow definition
Scheduling workflow
Operators
Command reference
Language API -Ruby
REST API
Internal architecture
Release NotesScheduling workflow
Setting up a schedule
ワークフローを定期的に実行するには、ワークフロー定義の最初に
schedule:
オプションを追加します。簡単なワークフローを生成します。日本時間の毎日9時に実行させる場合以下のようにスケジュールオプションを追加します。
workflows.digtimezone: Asia/Tokyo schedule: daily>: 09:00:00 +step1: sh>: echo start schedule ${session_time}Digdagサーバーを起動してworkflowsプロジェクトをDigdagサーバーにプッシュします。
$ digdag server --memory
$ digdag push workflows
ワークフローの詳細を見るとセッションに9時の実行履歴が追加されてStatusはSuccessになっています。
SessionsのSuccessをクリックすると実際試行情報も確認できます。
schedule:
オプション
構文 説明 例 hourly>: MM:SS 毎時 30分に実行 hourly>: 30:00 daily>: HH:MM:SS 毎時 7時に実行 daily>: 07:00:00 weekly>: DDD,HH:MM:SS 毎週9時に実行 weekly>: Sun,09:00:00 monthly>: D,HH:MM:SS 毎月一日の9時に実行 monthly>: 1,09:00:00 minutes_interval>: M 30分ごとにこのジョブを実行する minutes_interval>: 30 cron>: CRON 複雑なスケジューリングにはcron形式を使用 digdag checkコマンドは、最初のスケジュールがいつ開始するかを示します。
check$ digdag check 2020-07-12 09:23:10 +0900: Digdag v0.9.41 System default timezone: Asia/Tokyo Definitions (1 workflows): workflows (2 tasks) Parameters: {} Schedules (1 entries): workflows: daily>: "09:00:00" first session time: 2020-07-13 00:00:00 +0900 first scheduled to run at: 2020-07-13 09:00:00 +0900 (in 23h 36m 47s)注意
hourly、ddaily、weekly、またはmonthly使用する場合、セッション時間は実際の実行時間と異なる場合があります。 セッション時間は、実際の実行日の00:00:00です(hourlyの場合、時間は00:00に設定される)。スケジュール例(システム時間:2019-02-24 14:20:10 +0900)
セッション時間は全て「00:00:00」になっている
schedule first session time first scheduled to run at hourly>: 32:32 2019-02-24 14:00:00 +0900 2019-02-24 14:32:32 +0900 daily>: 10:32:32 2019-02-25 00:00:00 +0900 2019-02-25 10:32:32 +0900 weekly>: 2,10:32:32 2019-02-26 00:00:00 +0900 2019-02-26 10:32:32 +0900 monthly>: 2,10:32:32 2019-03-02 00:00:00 +0900 2019-03-02 10:32:32 +0900 Running scheduler
digdag scheduler
はスケジューラを起動します。$ digdag schedulerワークフロー定義を変更すると、スケジューラはdigdag.digファイルを自動的に再ロードするため、再起動する必要はありません。
以前9時に設定されている時間を12時に変更して
digdag scheduler
を実行してみたがエラーが出ました。
digdagのバグかもと思って調べたら以下のようなISSUEがありました。
schedulerはメンテも活発に行ってなさそうだったのでdigdag scheduler
よりはdigdag server
をお勧めします。workflows.digtimezone: Asia/Tokyo schedule: daily>: 12:00:00 +step1: sh>: echo start schedule ${session_time}$ digdag scheduler 1) Error injecting constructor, java.lang.IllegalArgumentException: Configured authenticatorClass not found: io.digdag.standards.auth.jwt.JwtAuthenticator at io.digdag.server.ServerModule$AuthenticatorProvider.<init>(ServerModule.java:176) while locating io.digdag.server.ServerModule$AuthenticatorProvider while locating io.digdag.spi.Authenticator for the 1st parameter of io.digdag.server.AuthRequestFilter.<init>(AuthRequestFilter.java:28) while locating io.digdag.server.AuthRequestFilter while locating io.digdag.server.AuthRequestFilter annotated with @com.google.inject.internal.UniqueAnnotations$Internal(value=3) 1) Error injecting constructor, java.lang.IllegalArgumentException: Configured authenticatorClass not found: io.digdag.standards.auth.jwt.JwtAuthenticator at io.digdag.server.ServerModule$AuthenticatorProvider.<init>(ServerModule.java:176) while locating io.digdag.server.ServerModule$AuthenticatorProvider while locating io.digdag.spi.Authenticator for the 1st parameter of io.digdag.server.AuthRequestFilter.<init>(AuthRequestFilter.java:28) while locating io.digdag.server.AuthRequestFilter while locating io.digdag.server.AuthRequestFilter annotated with @com.google.inject.internal.UniqueAnnotations$Internal(value=3)Checking scheduling status
クライアントモードのコマンドを使用して、スケジュールを管理できます。
クライアントモードのコマンドについては今後整理します。早めにみたい方は以下のリンク参照
http://docs.digdag.io/command_reference.html#client-mode-commandsスケジューラコマンドは、デフォルトで
http://127.0.0.1:65432
をリスンします。 127.0.0.1(localhost)からの接続のみ受け付けます。これはセキュリティ上の理由から、パブリックネットワークへのポートは開かれません。
リスンアドレスを変更するには、-bind ADDRESSオプションを使用してください。Setting an alert if a workflow doesn’t finish within expected time
例がなかったので簡単なコードで確認します。
ワークフローの実行時間が1秒以上かかる場合、警告を表示workflows.digtimezone: Asia/Tokyo schedule: daily>: 11:45:00 sla: duration: 00:00:01 +notice: sh>: echo alert! so slow +step1: sh>: ./your_script.sh
sleep 5
にして5秒遅延させるyour_script.sh#!/bin/bash sleep 5 echo start scriptサーバーのLogをみたい場合、起動の時にtask-logオプションを追加してください。
$digdag server --memory --task-log ./task_log 1秒で設定したワークフローの実行時間がが5秒かかったのでLogにaletメッサージが表示されています. StatusはSuccessになっています。Options
このパラメーターは、
fail:BOOLEAN
およびalert:BOOLEAN
オプションをサポートしています。
c
を設定すると、ワークフローが失敗します。
alert:true
は、上記の通知メカニズムを使用して通知を送信します。fail:trueを設定すると、ワークフローが失敗します。 alert:trueを設定すると、上記の通知メカニズムを使用して通知を送信します。上記の例では時間が過ぎてもStatusがSuccessになっていたのはこの設定がDefaultだと思います。
fail:true
を追加してみると実行時間が1秒超えた場合StatusがFailureになるのがわかる。add_fail_optiontimezone: Asia/Tokyo schedule: daily>: 12:03:00 sla: duration: 00:00:01 fail: true +notice: sh>: echo alert! so slow +step1: sh>: ./your_script.shSkipping a next workflow session
セッション間の継続時間よりも長い時間がかかるワークフロー(30分または60分ごとのセッションなど)を頻繁に実行している場合があります。
ワークフローの期間におけるこの変動は、いくつかの理由で発生する可能性があります。たとえば、通常処理しているデータの量が増加している場合が該当します。たとえば、1時間ごとに実行されるワークフローがあり、通常30分しかかからないとします。しかし、これは休日であり、現在はサイトの使用率が大幅に増加した結果ワークフローで1時間30分かかっている大量のデータが処理されています。
この期間中、2番目のワークフローが次の1時間実行開始されます。両方が同時に実行されるため、使用可能なリソースにさらに負担がかかります。この場合は、次の1時間のワークフローセッションをスキップし、代わりに後続のセッションを利用して2時間のデータを処理するのが最善です。これを行うために、以下を追加しました。
skip_on_overtime:true | false
: セッションが既に実行されている場合にスケジュールされたセッションの実行をスキップするかどうかを制御するために使用スケジュールされたワークフローセッションには、以前に実行されたセッション時間を含む
last_executed_session_time
変数があります。通常last_session_time
と同じです。skip_on_overtime:true
が設定されている場合かセッションが最初の実行
である場合は値が異なります。毎分実行されるワークフローを定義
workflows.digtimezone: Asia/Tokyo schedule: minutes_interval>: 1 skip_on_overtime: true +step1: sh>: ./your_script.shyour_script.sh#!/bin/bash sleep 120 echo start script毎分実行予約しても実際スクリプトは2分かかるサンプルです。
skip_on_overtime: true
により1分ごとに実行設定しても実際は最初の実行が終わった後の1分後に次の実行時間が決まります。
Skipping backfill
skip_delayed_by
オプションを使用するとbackfill
コマンドで、指定した時間だけ遅延したセッションの作成をスキップできます。Digdagが再起動すると、スケジュールのセッションが、last_session_timeの次のセッションまで自動的に作成されます。
schedule: minutes_interval>: 1 skip_delayed_by: 3m +setup: sh>: echo ${session_time}スケジュール再開の時(17分に再開)
再開時間の3分前の14分、15分、16分は再実行される、3分以上過ぎたセッションはSkipしている。
- 投稿日:2020-07-12T19:40:44+09:00
ActionTextのデータ構成はどうなってる?
個人的に
ActionText
を導入する機会があったのでメモ。
blogサービスのようなものを開発する際に、Article
モデルにどのようにActionText
を導入すればいいのか悩んだので、誰かの役に立てればと思います。ActionTextとは
rails6から使えるようになった機能の1つ。
Railsにリッチテキストコンテンツと編集機能を導入できる。今回やりたいこと
記事投稿サービスを作りたいと思います。
タイトル、説明文、本文の3つのデータをもつArticleモデルを作成し、本文の編集にActionTextを導入します。データ構成
ActionText
を導入するモデル作成
Article
モデルを生成します。
title
は記事のタイトル、description
は記事の説明文を表します。本文のデータを格納するカラムは作成しないことに注意してください。rails g model Article title:string description:text
ActionText
を導入する
ActionText
は以下のコマンドでインストールできます。rails action_text:installActionTextをインストールすると、2つのmigrationファイルが生成されます。
1つはActive Storage
に関わるファイルで、もう一つがActionText
のテーブルを作成するファイルです。
後者のテーブルがこの記事で説明したいデータ構成の鍵となります。リッチテキストコンテンツは独自のRichTextモデルに保存され、このモデルはアプリケーションの既存のあらゆるActive Recordモデルと関連付けられます。 あらゆる埋め込み画像(およびその他の添付ファイル)は自動的にActive Storageに保存され、includeされたRichTextモデルに関連付けられます。
https://railsguides.jp/action_text_overview.html#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB
class CreateActionTextTables < ActiveRecord::Migration[5.2] def change create_table :action_text_rich_texts do |t| t.string :name, null: false t.text :body, limit: 16777215 t.references :record, null: false, polymorphic: true, index: false t.datetime :created_at, null: false t.datetime :updated_at, null: false t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true end end end上記のデータを、
Article
モデルと紐づけることで、記事の内容をリッチテキストで編集することが可能になります。
データの紐付けは簡単で、以下のようにモデルに追加してください。article.rbclass Article < ApplicationRecord has_rich_text :content endどのようなデータ構造になっているのか
上記で説明した通り、
Article
テーブルでtitle
とdescription
を保持し、記事の内容(content
)は、ActionText::RichText
というテーブルに保持されています。以下のコンソールの結果を見るとイメージをしやすいと思います。# Articleモデルのデータを取得 irb(main):001:0> Article.all.first => #<Article id: 1, created_at: "2020-07-11 13:19:00", updated_at: "2020-07-11 13:19:00", title: "テスト", description: "テストです"> # Articleモデルに紐づいているActionText::RichTextのデータを取得 irb(main):002:0> Article.all.first.content => #<ActionText::RichText id: 1, name: "content", body: #<ActionText::Content "<div class=\"trix-conte...">, record_type: "Article", record_id: 1, created_at: "2020-07-11 13:19:00", updated_at: "2020-07-11 13:19:00"> # 記事の内容を取得するには? irb(main):003:0> Article.all.first.content.body => #<ActionText::Content "<div class=\"trix-conte...">
- 投稿日:2020-07-12T19:40:44+09:00
Action Textのデータ構成について
個人的に
ActionText
を導入する機会があったのでメモ。
blogサービスのようなものを開発する際に、Article
モデルにどのようにActionText
を導入すればいいのか悩んだので、誰かの役に立てればと思います。ActionTextとは
rails6から使えるようになった機能の1つ。
Railsにリッチテキストコンテンツと編集機能を導入できる。データ構成
ActionText
を導入するモデル作成
Article
モデルを生成します。
title
は記事のタイトル、description
は記事の説明文を表します。rails g model Article title:string description:text
ActionText
を導入する
ActionText
は以下のコマンドでインストールできます。rails action_text:installActionTextをインストールすると、2つのmigrationファイルが生成されます。1つは
Active Storage
に関わるファイルで、もう一つがActionText
のテーブルを作成するファイルです。後者のテーブルがこの記事ではターゲットとなります。ActionTextで編集された内容は、このテーブルに格納されるからです。class CreateActionTextTables < ActiveRecord::Migration[5.2] def change create_table :action_text_rich_texts do |t| t.string :name, null: false t.text :body, limit: 16777215 t.references :record, null: false, polymorphic: true, index: false t.datetime :created_at, null: false t.datetime :updated_at, null: false t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true end end end上記のデータを、
Article
モデルと紐づけることで、記事の内容をリッチテキストで編集することが可能になります。
データの紐付けは簡単で、以下のようにモデルに追加してください。article.rbclass Article < ApplicationRecord has_rich_text :content endどのようなデータ構造になっているのか
上記で説明した通り、
Article
テーブルでtitle
とdescription
を保持し、記事の内容(content
)は、ActionText::RichText
というテーブルに保持されています。以下のコンソールの結果を見るとイメージをしやすいと思います。# Articleモデルのデータを取得 irb(main):001:0> Article.all.first => #<Article id: 1, created_at: "2020-07-11 13:19:00", updated_at: "2020-07-11 13:19:00", title: "テスト", description: "テストです"> # Articleモデルに紐づいているActionText::RichTextのデータを取得 irb(main):002:0> Article.all.first.content => #<ActionText::RichText id: 1, name: "content", body: #<ActionText::Content "<div class=\"trix-conte...">, record_type: "Article", record_id: 1, created_at: "2020-07-11 13:19:00", updated_at: "2020-07-11 13:19:00"> # 記事の内容を取得するには? irb(main):003:0> Article.all.first.content.body => #<ActionText::Content "<div class=\"trix-conte...">結論
ActionTextは本当に簡単に導入できますが、データの仕組みが少し複雑なので注意してください。
また、私自身railsを勉強し始めて半年の未熟者ですので、間違いや不適切な表現があればご指摘ください。
- 投稿日:2020-07-12T19:05:07+09:00
Rails5でECサイトを作る⑧ ~Productモデル編、親子ともに条件つきで絞り込み表示~
はじめに
架空のベーカリーで買い物できるECサイトを作るシリーズ、Rails5でECサイトを作る⑦の続きです。
今回は商品の一覧、詳細を表示するProductモデルの実装です。商品の管理はadminサイト側で行うので、customerサイト側では画面を表示する処理のみになります。ただし、商品を束ねるGenreモデル、その下に属するProductモデルの両方に「販売中・停止」のステータスが存在します。商品ごとの販売中・停止を設定するほかに、ジャンルごとでも同様の設定を行えるということです。customerサイトで表示するのはGenre、Product両方で「有効」のものだけなので、その絞り込みが少々難しめです。
ソースコード
https://github.com/Sn16799/bakeryFUMIZUKI
Modelのアソシエーション
controller
app/controllers/products_controller.rbclass ProductsController < ApplicationController # サイドバー表示用 before_action :set_genres def show @product = Product.find(params[:id]) @cart = @product.cart_items.build end def index # ジャンルが有効 かつ 商品ステータスが有効 な商品を絞り込み @products = Product.includes(:genre).where(genres: {validity: true}).is_active.page(params[:page]).per(9) end private def set_genres @genre = Genre.is_valid end def product_params params.require(:product).permit(:name,:price,:image_id, :genre_id) end endindexにおいて、Genreモデル(親)のvalidityカラムと、Productモデル(子)のstatusカラムに条件を設定して絞り込みをかけています。これで、「ジャンルが有効」かつ「商品ステータスが有効」の商品群を取り出すことができました。また、ページャ(kaminari)を用意して、1ページにつき9個の商品が表示されるようにしています。
app/models/product.rbscope :is_active, -> { where(status: true) }app/models/genre.rbscope :is_valid, -> { where(validity: true) }controller内にあるis_activeとis_validは、いずれも商品、ジャンルの有効・無効を絞り込むスコープです。今回のアプリでは何回も行う処理ではないので別に必須ではないのですが、スコープを定義しておくことで後から修正しやすくなったり、コードソース上で何の処理をしているのか、視覚的に分かりやすくなったりします。
view
index画面
app/views/products/index.html.erb<div class="col-lg-10 space"> <div class="container"> <h2> <span style="display: inline-block;">全商品一覧</span> <span style="display: inline-block;">(<%= @products.count %>種)</span> </h2> </div> <div class="container"> <div class="row"> <% @products.each do |gp| %> <%= render 'products/box', product: gp %> <% end %> </div> </div> <%= paginate @products %> </div> <%= render 'genres/index', genres: @genres %>部分テンプレートは、'products/box'が商品1個ごとに写真・名前・値段などが載ったち小さなdivで、'genres/index'はジャンルごとに商品を見られるリンクを集めたサイドバーです。HTMLソースは前回の記事で公開しています。
show画面
app/views/products/show.html.erb<div class="col-lg-10 space"> <div class="container"> <div class="row"> <div class="col-lg-4 offset-lg-2"> <%= attachment_image_tag(@product, :image, :fill, 560, 420, fallback: "no_img.jpg") %> </div> <div class="col-lg-4"> <h3> <%= @product.name %> </h3> <h4>商品説明</h4> <%= @product.introduction %> <h4> <%= price_include_tax(@product.price) %> </h4> </div> </div> </div> <div class="container"> <div class="w-50 offset-lg-6"> <%= form_with(model: [@product, @cart], local: true, url: {controller: 'cart_items', action: 'create'}) do |f| %> <%= f.label :ご購入個数 %> <%= f.number_field :quantity, value: 1 ,min:1, max:99 %>個 <%= f.hidden_field :product_id, value: @product.id %> <%= f.submit "カートに入れる", class: "btn btn-danger" %> <% end %> </div> </div> </div> <%= render'genres/index', genres: @genres %>show画面では、商品の説明を見て、気に入ったものをカートに入れることができます。
空欄のフォームを表示するようにはなっていますが、CartItemControllerに何も書いていないため、現時点で「カートに入れる」ボタンを押すとエラーになります。ご注意を。
後記
商品がたくさん並んだ画面を作ったことで、見た目にはパン屋さんのサイトになりました。相変わらず機能面は皆無ですが。また、ページャや商品のボックスにCSSの加工を一切施しておらず、少し殺風景な感じは拭えません。装飾は最後にまとめて行う予定なので、まずは機能の完成を急ぎたいところ。
このECサイトの原型となっているのはスクールのチーム実装で作ったアプリなのですが、今見るとバリデーションなど意外と抜け落ちているところがあります。3人がかりで取り組んでも見逃す部分はあるのだなあと思いましたが、もしかしたら単にそこまで細かく要件定義された課題ではなかっただけかも知れません。
今回は2回目の開発なので、その辺りもこだわって実装していきたいと思います。次回へ続く!
参考
- 投稿日:2020-07-12T18:01:15+09:00
Ruby で 繰返し二乗法 を解くなら Integer#pow がお薦め
はじめに
AtCoder Problems の Recommendation を利用して、過去の問題を解いています。
AtCoder さん、AtCoder Problems さん、ありがとうございます。事の始まり
Ruby と Perl と Java と Python で解く AtCoder ATC 002 B 繰返し二乗法 でコメントをいただきました。
Ruby だと専用メソッド Integer#pow がありますね。
早速試しました。
AtCoder Typical Contest 002 B - n^p mod m
pow.rbn, m, p = gets.split.map(&:to_i) puts n.pow(p, m)zisaku.rbn, m, p = gets.split.map(&:to_i) def mpow(n, p, mod) r = 1 while p > 0 r = r * n % mod if p % 2 == 1 n = n * n % mod p >>= 1 end r end puts mpow(n, p, m)
Integer#pow 自作mpow コード長 (Byte) 50 183 実行時間 (ms) 68 62 メモリ (KB) 14356 14356 AtCoder Regular Contest 066 C - Lining Up
pow.rbn = gets.to_i a = gets.split.map(&:to_i) h = Hash.new(0) a.each do |x| h[x] += 1 end f = true h.each do |k, v| if n.odd? && k == 0 if v != 1 f = false break end elsif v != 2 f = false break end end MOD = 1_000_000_007 puts (f ? 2.pow(n / 2, MOD) : 0)
Integer#pow 自作mpow コード長 (Byte) 305 438 実行時間 (ms) 101 100 メモリ (KB) 22676 22488 エイシング プログラミング コンテスト 2020
pow.rbn = gets.to_i x = gets.chomp xs = x.to_i(2) xc = x.count('1') def mpow(n, p, mod) return 0 if mod.zero? n.pow(p, mod) end def popcount(u) return 0 if u.zero? a = u % u.to_s(2).count('1') 1 + popcount(a) end xsp = mpow(xs, 1, xc + 1) xsm = mpow(xs, 1, xc - 1) n.times do |i| if x[i] == '0' y = xsp + mpow(2, n - i - 1, xc + 1) y = mpow(y, 1, xc + 1) elsif xc == 1 puts '0' next else y = xsm - mpow(2, n - i - 1, xc - 1) y = mpow(y, 1, xc - 1) end puts popcount(y) + 1 end
Integer#pow 自作mpow コード長 (Byte) 541 629 実行時間 (ms) 421 625 メモリ (KB) 14704 15392 いずれも同等以上の成績でした。
ruby
の時代が来ましたね。まとめ
- Integer#pow を使おう
- Ruby の時代が来た
参照したサイト
instance method Integer#**
Ruby と Perl と Java と Python で解く AtCoder ATC 002 B 繰返し二乗法
Ruby と Python で解く AtCoder ARC 059 C 最小二乗法
Ruby と Python で解く AtCoder AISING2020 D 繰返し二乗法
- 投稿日:2020-07-12T17:32:39+09:00
VagrantでRuby on Railsの開発環境を構築する手順
環境
macOS 10.15.4
Vagrantをインストール
Vagrantをホームページからインストールします。
Cent OSをインストール
まずはVagrantがインストールできているか確認をします。
terminal$ vagrant -v Vagrant 2.2.9ディレクトリを作成し、移動します。
terminal$ mkdir -p vagrant/centos7 $ cd vagrant $ cd centos7移動したディレクトリで下記のコマンドを実行します。
terminal$ vagrant init centos/7インストール完了後、エディタを開き下記の1行をコメントアウトから外します。
Vagrantfile# config.vm.network "private_network", ip: "192.168.33.10"
下記のコマンドを実行し、少し待ちます...
terminal$ vagrant upCentOSにログイン
下記のコマンド実行しログインします。
terminal$ vagrant ssh [vagrant@localhost ~]$rbenv をインストール
yumパッケージを準備します。
terminal$ sudo yum install -y git gcc openssl-devel readline-devel zlib-devel sqlite-devel gcc-c++ libicu-devel cmake vimrbenvをインストールします。
terminal$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv $ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile $ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile $ source ~/.bash_profileインストールが完了しているかどうか確認をします。
terminal$ rbenv --version rbenv 1.1.2-30-gc879cb0ruby-buildプラグインを追加
下記のコマンドを実行しruby-buildプラグインを追加します。
terminal$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-buildRubyをインストール
Rubyをインストールします。
※余談ですがわたしが他者様のサイトを参照にしたときにバージョンが2.4でした。しかしRails5以上と現在主流のRailsのバージョンには2.5以上が必要になりますので、バージョン指定などに気をつけてください。
terminal$ rbenv install 2.7.1 $ rbenv global 2.7.1 $ rbenv rehash $ ruby -vインストールの完了が確認できましたら次に進みます。
Bundlerをインストール
Bundlerのインストールを行うため下記のコマンドを実行します。
terminal$ gem install bundler $ rbenv rehash $ bundle -v Bundler version 2.1.4Ruby on Railsをインストール
インストールするためのディレクトリの作成と移動を行います。
terminal$ mkdir -p app/memo_app $ cd app $ cd memo_appそしてインストールするためのコマンドを実行します。
terminal$ bundle init $ sed -i 's/# gem "rails"/gem "rails", "~> 5.1.0"/g' Gemfile $ bundle install --path vendor/bundler $ bundle exec rails new . $ sed -i "s/# gem 'therubyracer'/gem 'therubyracer'/g" Gemfile $ bundle installサーバーを起動
下記のコマンドを実行し下記のアドレスにアクセスしてください。
terminal$ ./bin/rails s -b 0.0.0.0これで手順は終了になります^_^
(補足)シャットダウンとログイン/ログアウトについて
一度閉じてしまったときや再度開くときに必要になると思いますのでシャットダウンとログイン/ログアウトについても説明します。
ログアウト
terminal# CentOSからログアウトする場合のコマンド $ exit #CentOSをシャットダウンする場合のコマンド $ vagrant haltログイン
terminal# Cent OSにログインする場合のコマンド $ vagrant up $ vagrant ssh [vagrant@localhost ~]$Vagrantの開発環境でも高機能エディタ(VSCode)でアプリを作成できるようにする手順
Vagrantの仮想環境が構築されましたがこれを高機能エディタ(VSCode)でも作成や編集が行えるように接続する方法を解説していますので合わせてご覧ください^_^
- 投稿日:2020-07-12T16:51:56+09:00
hamlでYouTube動画をDBから呼び出して埋め込み表示させてみた
自作アプリでYouTubeの動画を埋め込み表示させてみた時に見付けた方法
自作アプリの作成でお気に入りの動画を一旦DBに保存して、呼び出す際に埋め込み表示させたかったので、色々模索したやり方を紹介します。
一般的な埋め込みの方法
好きなYouTube動画の共有ってところを選ぶ
これをHTML(haml)に直接貼り付ければ完成
ただ、これでDBに投稿して、hamlファイルから呼び出そうとすると文字列で出てしまったので、それを解消する方法です。
実際に行ったやり方
%iframe#player{frameborder: "0", height:"390", src: (movie.url), type: "text/html", width: "640"} ※今回はMovieモデルと言うのを作って、カラム名をurlにしてます簡単に言うと、
・外枠の部分はhamlファイルにしておく(サイズ等はお好みで変えられるはずです)
・DBにはURL部分のみ保存→呼び出す記述にしてます
・タイトルや内容なども別のカラムで保存こうする事で、DBに投稿→呼び出しの際も埋め込み表示に出来ました。
実際の画像(まだ開発中ですが)
1つ注意ポイント
私が使ったURLですが、この青下線の部分です(ダブルクォーテーションは要らないです)
こっちの青下線のURLではダメでした。
おそらく埋め込み用のURLでは無いからだと思います。以上、同じ様な実装をされたい方の参考になれば幸いです
- 投稿日:2020-07-12T16:14:05+09:00
Rubyで値オブジェクトの親クラスを作ってみた
ドメイン駆動設計にかぶれて値オブジェクトを作ってみたんですが、結構面倒くさかったんで、親クラスを作ってみました。
(あくまでも個人開発で開発スピードを早めるために作っていますので、製品でそのまま使えるかは保証しません)値オブジェクトとは
https://qiita.com/kichion/items/151c6747f2f1a14305cc
に書いてあるような特徴を持つオブジェクトです。Rubyで書いてみると↓みたいなオブジェクトになります。この例はユーザの名前を入れる値オブジェクトです。
(正統な定義をするならequal
的なものを定義したほうが良いんでしょうが、あまり使わないので今は定義していません。)domain/user/name.rbmodule Domain::User class Name attr_reader :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end def update_first_name(first_name) self.new(first_name, @last_name) end def update_last_name(last_name) self.new(@first_name, last_name) end def fullname "#{@last_name} #{@first_name}" end end end使うのには便利なんですが、いちいちこのお決まりのフィールドのゲッター、セッターの何行かを書くのが面倒くさいんですよね。
なので、これらを勝手に定義してくれる親クラスを作ってみました。値オブジェクトの親クラス
domain/base/value_object.rbmodule Domain::Base class ValueObject attr_reader :changed_fields FIELDS = [] class << self # 次のようなメソッドを定義 new_date = date.update_month(11) def attr_updater(*attrs) attrs.map(&:to_sym).each do |defining_attr| define_method("update_#{defining_attr}") do |defining_value| values = attrs.map { |attribute| attribute == defining_attr ? defining_value : instance_variable_get("@#{attribute}")} changed_fields = @changed_fields.include?(defining_attr) ? @changed_fields : @changed_fields + [defining_attr] self.class.new(*values, changed_fields: changed_fields) end end end end # NOTE # - 基本的に値オブジェクト側ではFIELDSとアクセサだけ定義させる # - initializeは親のこのクラスの物を使わせる。 # - initializeの独自実装をするのは可能。しかしその場合、値オブジェクト側からこの親クラスのinitializeを使うだけで、値オブジェクト使用者にはこのクラスのinitializeを直接は使わせない # - changed_fieldsなどの変更は内部でのみ行う # - 値オブジェクト自体の初期化引数はできるだけ単純になるようにケア def initialize(*field_values, fields: self.class::FIELDS, changed_fields: []) define_fields(fields, field_values) @changed_fields = changed_fields end private def define_fields(fields, field_values) fields.zip(field_values).each do |field, field_value| instance_variable_set("@#{field}", field_value) end end end endこれを使うと以下の書き方で、元々のメソッドが提供できます。
domain/user/name.rbmodule Domain::User class Name < ::Domain::Base::ValueObject FIELDS = %I(first_name last_name) attr_reader *FIELDS attr_updater*FIELDS def fullname "#{@last_name} #{@first_name}" end end end備考
- メタプログラミングの黒魔術を使いすぎると、追えなくなる(grepでも目視でも)ので、複雑なメソッドは定義していません
attr_reader
とかも自動化できるけど、attr_reader
とかがあると読み手に負荷をかけないので、基本のRubyの書き方ができるところは踏襲しています。- この書き方だと、
initialize
時にFILEDどおりの順番で引数を入れることを暗黙に示していており、それを明示していないんですがが、自分ではその問題をわかっているので、今の所運用で回避しています。changed_fields
はDB更新時に更新があったフィールドだけ更新したかったので好みで作りました。
- 投稿日:2020-07-12T16:02:11+09:00
[Rails] enumを使って下書き機能を作った
はじめに
下書き機能を作ろうと思った時にあまり記事がなかったので記録として残しておこうと思いました。
開発環境
ubuntu(WSL) Ruby 2,5.1 Rails 6.0.2事前準備
以下の機能は作成済みとします。
- 投稿機能・詳細・削除・編集(Post)
- ユーザーの作成・詳細(User)
models/post.rbbelongs_to :usermodels/user.rbhas_many :microposts, dependent: :destroyPostテーブルにカラムを追加する
まずはPostモデルにstatusカラムを追加しboolean型にします。
booleanでなくintergerでも可能です。
以下のコマンドを入力します。bin/rails g migration AddStatusToPost status:booleanマイグレーションファイルを編集します。
migrationfile.rbdef change add_column :microposts, :status, :boolean, default: true, null: false end編集したらマイグレートします。
Postモデルにenumを追加する。
models/post.rbenum status: { draft: false, published: true }statusカラムのdraft(下書き)をfalseに指定し、statusカラムのpublished(公開)をtrueに指定します
ユーザーごとの情報を取得
@user
でuserのidを取得。users_controller.rb#下書き用 def confirm @user = User.find(params[:user_id]) @microposts = @user.microposts.draft.page(params[:page]) end #公開用 def show @user = User.find(params[:id]) @microposts = @user.microposts.published.page(params[:page]) endルーティングを追加
collection
をつけるとURLにidが付かなくなりますroutes.rbresources :users do get 'confirm' endViewの設定
関係あるところだけを抜粋しました。
view/users/show.html.slim//ユーザーの詳細画面 = link_to "投稿一覧", @user = link_to "お気に入り", user_likes_path(current_user) = link_to "下書き一覧", user_confirm_path(current_user) //自分の投稿を表示する - if @microposts.present? = render "microposts/list", microposts: @microposts - else h4 投稿はありません下書き一覧は自分の好みに合わせて書いてください。
view/users/confirm.html.slimh4 下書き一覧 table.table.table-hover thead.thead-default tr th = Micropost.human_attribute_name(:title) th = Micropost.human_attribute_name(:content) th = Micropost.human_attribute_name(:created_at) th tbody - @microposts.each do |micropost| tr td = link_to micropost.title, micropost td = link_to micropost.content, micropost td
icroposts/list
の中身は上記と同じです。終わりに
間違いがありましたら編集リクエスト、コメントをお願いします。
参考文献
- 投稿日:2020-07-12T15:48:26+09:00
投稿ページのビュー画面を直す
投稿はできているが、何故はみ出るんだ?
インデントも問題無いtop_page>index.html.haml.top %ul.top__side ジャンル %li.top__side__love 恋愛 %li.top__side__work 仕事 %li.top__side__money お金 %li.top__side__friend 友人 %li.top__side__school 学校 %li.top__side__other その他 .top__consultation .top__consultation__title お悩み一覧 .top__consultation__text - @toppages.each do |top_page| = top_page.contents .top__consultation__text__time = top_page.created_at色々試行錯誤した結果each文の後が元の定義したクラスの中に収まらないことが判明しました。
これ、何ででしょうかね??ということでそれならとeach文を一番最初につけました
top_page>index.html.haml- @toppages.each do |top_page| .top %ul.top__side ジャンル %li.top__side__love 恋愛 %li.top__side__work 仕事 %li.top__side__money お金 %li.top__side__friend 友人 %li.top__side__school 学校 %li.top__side__other その他 .top__consultation .top__consultation__title お悩み一覧 .top__consultation__text = top_page.contents .top__consultation__text__time = top_page.created_at割愛してますが、インデントを最初の"top"と合わせたらSyntaxError
エラーになったので地味に気をつけたいところ。全て1個インデントを下げる必要があります(当たり前だけど)ということで解決です。
ただ何故はみ出てしまうのか原因はわかりましたが、言語化できません。
htmlのルールと言ってしまえば終わりな気はしますが。訂正
修正したコードで複数投稿したら一番上にeach分があるためにお悩み一覧や左のカテゴリまで
全て表示されていました汗
正しくは下記ですねtop_page>index.html.haml.top %ul.top__side ジャンル %li.top__side__love 恋愛 %li.top__side__work 仕事 %li.top__side__money お金 %li.top__side__friend 友人 %li.top__side__school 学校 %li.top__side__other その他 .top__consultation .top__consultation__title お悩み一覧 - @toppages.each do |top_page| .top__consultation__text = top_page.contents .top__consultation__text__time = top_page.created_atただ要領は変わらないですね。インデント一個下げただけであっさり解決しました笑
- 投稿日:2020-07-12T14:28:17+09:00
フロント側でAPIからのSet-Cookieを無効にする方法
背景
deviseを使って作成したRails製Webアプリのモバイルアプリ版を作成するため、devise-token-auth でトークン認証出来るようにしようとしたところCookieが効いて別のユーザー情報が返ってきてしまう。
やっていたこと
devise-token-authで作成したエンドポイントをフロントから叩いてチェックしていました。
直接curlで叩いたら上手くいくのに....解決策
import { NativeModules } from 'react-native'; NativeModules.Networking.clearCookies(() => { });参考記事そのままですが、フロント(ReactNative)側でこのようにするとAPIから送られたCookieがフロント側でクリアされ期待した通りの動作をするようになりました。
今回はフロント側で対応しましたが、バックエンド側でdevise-token-authのcontrollerをオーバーライドして対応することも出来るようです。
なぜCookieが保存されていたか
ネイティブアプリではデフォルトでCookieが保存されるようです。
ちなみに、RailsのAPIモードではデフォルトでCookieが使えないようになっているものの、今回は既存のWebアプリにdevise-token-authでトークン認証をつけようとしていたためCookieが渡されるようになっていました。参考記事
https://qiita.com/kakken1988/items/504e679504b9e710cf21
ちなみに
Memopicというアルバム・写真共有サービスを作っています。是非使ってみてください!
ご意見・ご要望などあればこちら(memopic.bamboo@gmail.com)までお願いします。ここだけでなくdevise-token-authを導入する時もdeviseとの共存させるのに少しハマったので、そこについての記事も近々書こうと思ってます〜
ご指摘、ご意見お待ちしてます!
- 投稿日:2020-07-12T14:05:57+09:00
Railsで表示時間を日本時間に設定する方法
やりたいこと
投稿時間を国際標準時間から日本時間の表示に変更したい
設定の方法
config/application.rb
内にconfig.time_zone = 'Tokyo'
の記述を追加application.rb# ↑これ以前のコードは割愛 module App class Application < Rails::Application config.time_zone = 'Tokyo' end end
- サーバーが立ち上がっている場合は一度シャットアウトして再度立ち上げ直す
※dockerを起動している場合は、下記でコンテナを立ち上げ直す(ターミナルで下記コマンドを実行)
docker-compose stop
docker-compose up -d
これで日本時間で表示されるが、フォーマットを別途変更する必要あり
strftimeメソッド
を使って、表示フォーマットを変えたい箇所にメソッドを当てるsample.html.erb# 下記はサンプル <td><%= @tweet.created_at.strftime('%Y年%m月%d日 %H時%M分') %></td>変換のメソッドを定義する方法
やること:Initializeにフォーマット変換を定義する
config/initializers配下
にtime_formats.rb
というファイルを作成するTime::DATE_FORMATS[:datetime_jp] = '%Y年%m月%d日 %H時%M分'
と記述するtime_formats.rbTime::DATE_FORMATS[:datetime_jp] = '%Y年%m月%d日 %H時%M分'※ [:datetime_jp] は任意の命名でOK
※ '%Y年%m月%d日 %H時%M分' には変換したいフォーマットを記述する
- 使いたい箇所(viewファイル内)で .to_s(:datetime_jp]) と記述して使う
sample.html.erb# 下記はサンプル <td><%= @tweet.created_at.to_s(:datetime_jp) %></td>
- 投稿日:2020-07-12T13:36:32+09:00
【Rails】プロフィールに自己紹介機能を実装するシンプルな方法
プロフィールに自己紹介機能を実装する
ポートフォリオ完成後、追加実装したので順序を備忘録として残します。
ユーザー名・email・パスワードなどの既存カラムに、introductionカラムを新たに追加します。
なるべくシンプルに記します!どうやって?
以下の順序です。
- git checkout -b introduction (ブランチ切る)
- usersテーブルにintroductionカラムを追加する
- データベースに反映させる
- usersコントローラーに必要コードの追記
- userモデルに必要コードの追記
- ユーザー詳細ページ(MYPAGE)に表示させる
- ユーザー編集ページ(EDIT)に表示させる
- 完成!
以下順番に記します!
1. git checkout -b introduction (ブランチ切る)
新たな機能を実装するため、ブランチを切リます。
2. usersテーブルにintroductionカラムを追加する
ターミナル$ rails generate migration AddIntroductionToUsers introduction:text invoke active_record create db/migrate/20200712005652_add_introduction_to_users.rb上記rails gコマンドでintroductionカラムを追加します。
3. データベースに反映させる
ターミナル$ docker-compose run web rails db:migrate == 2020~~~~ AddIntroductionToUsers: migrating =========================== -- add_column(:users, :introduction, :text) -> 0.0518s == 2020~~~~ AddIntroductionToUsers: migrated (0.0519s) ==================4. usersコントローラーに必要コードの追記
private methodに追記
introduction属性を付与するusers_controller.rbdef user_params_update params.require(:user).permit(:name, :email, :image, :introduction) # introdution追加 endこれでintroductionをupdate可能になります
5. userモデルに必要コードの追記
バリデーションを追加します。自己紹介は50文字以内で入力させます。
文字数は自由に設定してください。
※presenseですが、falseにしないと新規登録時に作用してintroductionがnilとなって新規登録できなくなりますので注意してください。新規登録時に自己紹介も入力必要な場合とするならtrueでOKです。user.rbvalidates :introduction, presence: false, length: { maximum: 50 } # 自己紹介の最高文字数は50文字6. ユーザー詳細ページ(MYPAGE)に表示させる
show.html.slim= @user.introductionSCSSなどで適宜修正してください
7. ユーザー編集ページ(EDIT)に表示させる
ユーザー編集はユーザー名・emailのみでしたがそこにintroductionを追記します。
edit.html.slim.form-group = f.label :introduction = f.text_area :introduction, class: 'form-control', id: 'user_introduction'完成
以上で自己紹介をusersテーブルに追加し、introduction属性を追加完了しました。
途中でintroductionをintroduceと書いていたりしたので、皆様も注意してください!
- 投稿日:2020-07-12T13:29:53+09:00
Ruby と Python で解く AtCoder AISING2020 D 繰返し二乗法
はじめに
エイシング プログラミング コンテスト 2020 に参加しました。
AtCoder さん、AtCoder Problems さん、ありがとうございます。今回のお題
AtCoder エイシング プログラミング コンテスト 2020 D - Anything Goes to Zero
Difficulty: 1294今回のテーマ、繰返し二乗法
0 1 1 0 1 10進数表記 元の数値 $$0*2^4$$ $$1*2^3$$ $$1*2^2$$ $$0*2^1$$ $$1*2^0$$ 13 0桁目ビット反転 $$1*2^4$$ 13+16=29 1桁目ビット反転 $$0*2^3$$ 13- 8= 5 2桁目ビット反転 $$0*2^2$$ 13- 4= 9 3桁目ビット反転 $$1*2^1$$ 13+ 2=15 4桁目ビット反転 $$0*2^0$$ 13- 1=12 与えられた数値を
01101
としますと、上の表の様に分解できます。
また、分配則として下記が成り立ちますので、
$$(a+b)\%m=(a\%m+b\%m)\%m$$
ビット反転した桁の数値を出し入れすることにより、高速に全体の数値を求めることができます。Ruby
ruby.rbn = gets.to_i x = gets.chomp xs = x.to_i(2) xc = x.count('1') def mpow(n, p, mod) return 0 if mod.zero? r = 1 while p > 0 r = r * n % mod if p & 1 == 1 n = n * n % mod p >>= 1 end r end def popcount(u) return 0 if u.zero? a = u % u.to_s(2).count('1') 1 + popcount(a) end xsp = mpow(xs, 1, xc + 1) xsm = mpow(xs, 1, xc - 1) n.times do |i| if x[i] == '0' y = xsp + mpow(2, n - i - 1, xc + 1) y = mpow(y, 1, xc + 1) elsif xc == 1 puts '0' next else y = xsm - mpow(2, n - i - 1, xc - 1) y = mpow(y, 1, xc - 1) end puts popcount(y) + 1 end以前投稿しました Ruby と Perl と Java と Python で解く AtCoder ATC 002 B の
mpowメソッド
をコピペ使用します。mpow.rbdef mpow(n, p, mod) return 0 if mod.zero? r = 1 while p > 0 r = r * n % mod if p & 1 == 1 n = n * n % mod p >>= 1 end r end但し、今回は除数が
0
になる可能性がありますので、それに対応しています。inout.rbn.times do |i| if x[i] == '0' y = xsp + mpow(2, n - i - 1, xc + 1) y = mpow(y, 1, xc + 1) elsif xc == 1 puts '0' next else y = xsm - mpow(2, n - i - 1, xc - 1) y = mpow(y, 1, xc - 1) end puts popcount(y) + 1 endここで、ビット反転した桁の数値の出し入れを行っています。
ここでも、除数が0
の場合の処理が必要です。popcount.rbdef popcount(u) return 0 if u.zero? a = u % u.to_s(2).count('1') 1 + popcount(a) end2回目以降の
popcount
を再帰計算しています。
ここをメモ化するともう少し速くなります。Python
python.pyfrom sys import stdin def mpow(n, p, mod): if mod == 0: return 0 r = 1 while p > 0: if p & 1 == 1: r = r * n % mod n = n * n % mod p >>= 1 return r def popcount(u): if u == 0: return 0 a = u % bin(u).count('1') return 1 + popcount(a) def main(): input = stdin.readline n = int(input()) x = '0b' + input() xs = int(x, 0) xc = x.count('1') xsp = mpow(xs, 1, xc + 1) xsm = mpow(xs, 1, xc - 1) for i in range(2, n + 2): if x[i] == '0': y = xsp + mpow(2, n - i + 1, xc + 1) y = mpow(y, 1, xc + 1) elif xc == 1: print(0) continue else: y = xsm - mpow(2, n - i + 1, xc - 1) y = mpow(y, 1, xc - 1) print(popcount(y) + 1) main()
Ruby Python コード長 (Byte) 629 872 実行時間 (ms) 625 1048 メモリ (KB) 15392 9636 まとめ
- AISING2020 D を解いた
- Ruby に詳しくなった
- Python に詳しくなった
参照したサイト
pythonで2進数を表す
- 投稿日:2020-07-12T13:08:42+09:00
undefined method `user_signed_in?'と出たときの対処法
日々学んだことやつまずいたことについてまとめていきます。
記載に誤りがありましたら、ご指摘していただけると助かります!
いつも他のかたの記事に助けられているので、少しでもお役に立てればと思います。どういうエラーなのか
deviseで使えるようになる、 user_signed_in ヘルパーメソッドが定義されていないというエラー。
どんなときに起こるのか
1.deviseがインストールされていないとき。
2.routes.rbにdevise_for :users などの記述がないとき。対処法
1.
Gemfileに以下を記述。gem 'devise'ターミナルで以下を実行。
gemをインストール
bundle install設定ファイルを作成
rails g devise:installログイン機能に対応したモデルを作成
rails g devise userログイン機能に関連するテーブルを作成
bundle exec rake db:migrate2.
routes.rbに以下を記述devise_for :users
- 投稿日:2020-07-12T12:01:57+09:00
【rails】ランダムのパスワードを作成しパスワードフィールドに入力する方法
- 投稿日:2020-07-12T12:01:57+09:00
【rails】ランダム英数字のパスワードを作成しパスワードフィールドに入力する方法
パスワードフィールドにパスワードを生成して入力させよう
ポートフォリオの投稿機能にパスワード機能を設定したのですが、
パスワードを自動的に生成、入力までしてくれる機能あったら便利じゃん。ということで作りました。
以下画像のように「パスワードを生成する」ボタンをクリックすると、
自動的に入力できるようにしましょう!
パスワードを生成する
まずはコントローラーでランダムの英数字を作れるようにします。
topics_controller.rbdef new @topic = Topic.new @password = SecureRandom.alphanumeric(6) -> ランダムの英数字(A-Z, a-z, 0-9)を生成 (6)は6桁、指定なしの場合は16桁になります。 endパスワードをパスワードフィールドに入力しよう
次に先ほど作したパスワードをpassword_field(今回はtext_fieldにしています)に
入力できるようにします。new.html.erb<div class="topic-new-wrapper" > <div class="container"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for @topic do |f| %> <div class="form-group"> <%= f.label :password, 'password(任意)' %> <%= f.text_field :password, class: 'form-control', id: 'password' %> <%= button_tag 'パスワードを生成する', id: 'auto-fill-link' %> </div> <%= f.submit 'パスワード登録', class: 'btn btn-black btn-block' %> <% end %> </div> </div> </div> </div> <script> $(function(){ autoFill(); function autoFill() { $('#auto-fill-link').click(function(){ $('#password').val("<%= @password %>"); }); } }) </script>auto-fill-linkというidを含むリンクがクリックされると、passwordというidを含む
fieldに@passwordが入力されます。ただこれだとbutton_tagがクリックされたときにsubmitされてしまうため、
ただのボタンとして利用する際は以下のコードを追加してあげます。type: "button"
new.html.erb<div class="topic-new-wrapper" > <div class="container"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for @topic do |f| %> <div class="form-group"> <%= f.label :password, 'password(任意)' %> <%= f.text_field :password, class: 'form-control', id: 'password' %> <%= button_tag 'パスワードを生成する', id: 'auto-fill-link', type: "button" %> </div> <%= f.submit 'パスワード登録', class: 'btn btn-black btn-block' %> <% end %> </div> </div> </div> </div> <script> $(function(){ autoFill(); function autoFill() { $('#auto-fill-link').click(function(){ $('#password').val("<%= @password %>"); }); } }) </script>まとめ
パスワードの生成から入力までしてくれる機能を作成しました。
簡単なので上記のコードぜひ使ってくださいね。
- 投稿日:2020-07-12T12:01:57+09:00
【rails】ボタンをクリックするとランダムの英数字パスワードを作成、パスワードフィールドに入力する方法
パスワードを自動生成したい
ポートフォリオの投稿機能にパスワード機能を設定したのですが、
パスワードを自動的に生成、入力までしてくれる機能あったら便利じゃん。ということで作っていきましょう!
この記事のゴール
「パスワードを生成する」ボタンをクリックして
自動的に入力できるようにしましょう!
前提
rails:5.2
ruby:2.6.3
bootstrap
jqueryパスワードを生成する
まずはコントローラーでランダムの英数字を作れるようにします。
topics_controller.rbdef new @topic = Topic.new @password = SecureRandom.alphanumeric(6) -> ランダムの英数字(A-Z, a-z, 0-9)を生成 (6)は6桁、指定なしの場合は16桁 endボタンクリックでフィールドに入力できるようにする
次に先ほど作したパスワードをpassword_field(今回はtext_fieldにしています)に
入力できるようにします。new.html.erb<div class="topic-new-wrapper" > <div class="container"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for @topic do |f| %> <div class="form-group"> <%= f.label :password, 'password(任意)' %> <%= f.text_field :password, class: 'form-control', id: 'password' %> <%= button_tag 'パスワードを生成する', id: 'auto-fill-link' %> </div> <%= f.submit 'パスワード登録', class: 'btn btn-black btn-block' %> <% end %> </div> </div> </div> </div> <script> $(function(){ autoFill(); function autoFill() { $('#auto-fill-link').click(function(){ $('#password').val("<%= @password %>"); }); } }) </script>"auto-fill-link"というidを含むリンクがクリックされると、"password"というidを含む
fieldに@passwordが入力されます。ただこれだとbutton_tagがクリックされたときにsubmitされてしまうため、
ただのボタンとして利用する際は以下のコードを追加してあげます。type: "button"
new.html.erb<div class="topic-new-wrapper" > <div class="container"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for @topic do |f| %> <div class="form-group"> <%= f.label :password, 'password(任意)' %> <%= f.text_field :password, class: 'form-control', id: 'password' %> この行-> <%= button_tag 'パスワードを生成する', id: 'auto-fill-link', type: "button" %> </div> <%= f.submit 'パスワード登録', class: 'btn btn-black btn-block' %> <% end %> </div> </div> </div> </div> <script> $(function(){ autoFill(); function autoFill() { $('#auto-fill-link').click(function(){ $('#password').val("<%= @password %>"); }); } }) </script>まとめ
パスワードの生成から入力までしてくれる機能を作成しました。
簡単なので上記のコードぜひ使ってくださいね。
- 投稿日:2020-07-12T11:09:04+09:00
【Rails】RspecでCapybaraを使用した際に「undefined method `visit'」というエラーが出た場合の解決方法
開発環境
・Ruby: 2.5.7
・Rails: 5.2.4
・rspec-rails: 4.0.1
・Capybara: 3.32.2
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina前提
下記実装済み。
・Slim導入
・Bootstrap3導入
・Font Awesome導入
・ログイン機能実装
・投稿機能実装原因
Capybaraが読み込めていない。
解決方法
1.
require
で読み込むspec_helper.rbrequire 'capybara/rspec' # 追記 RSpec.configure do |config| end2. 解決方法1で解決しない場合は
DSL
で強制的に読み込むspec_helper.rbrequire 'capybara/rspec' RSpec.configure do |config| config.include Capybara::DSL # 追記 end
- 投稿日:2020-07-12T10:56:25+09:00
booleanのvalidateについて
booleanのカラムについて、バリデーションをかけたがvalid?がtrueにならず、期待していた実行とは違うものになってしまった!
そこで、booleanのカラムについて調べたことを備忘録としてまとめておく!
ダメなパターン:Notnull制約でfalseが入っているので、presence: trueをすれば良いと思い下記のようにした!
validates :check, presence: trueしかし、これだとcheck=falseの時にエラーが出てしまう!
調べてみると、下記のようにする必要があることがわかった!
validates :check, inclusion: {in: [true, false]}
- 投稿日:2020-07-12T08:43:11+09:00
Ruby on Rails seeds.rbファイルを使って、コマンド1つでレコードを大量作成
seeds.rbとは?
app/db ディレクトリにあるファイルで、レコードの作成に使うファイル。
レコードを作成する際に手作業で1つ1つデータを登録しなくても、
seeds.rbを使用すると大量のレコードをコマンド1つで作成することができる。seeds.rbを使用したレコード作成方法
- seeds.rb ファイルにレコード作成の処理を記述
- rails db:seed を実行
- seeds.rb ファイルの処理が実行される
例:usersテーブルに100人分の名前とメールアドレスを登録
app/db/seeds.rb に以下のように記述し、
seeds.rb100.times do |n| User.create(name: "name#{n}", email: "mail#{n}@gmail.com") endrails db:seed を実行すると、
users テーブルには name, email の値がそれぞれ"name0, mail0@gmail.com",
"name1, mail1@gmail.com",
・
・
・
"name99, mail99@gmail.com"のようにレコードが100件登録される。
- 投稿日:2020-07-12T04:34:04+09:00
Ruby + SinatraでLINE Botを作ろう 〜 オウム返し編 〜
こんにちは。
この記事では、複数回に渡ってRubyとSinatraを使って
- 本の裏にあるISBNコードを送信すると検索して本の画像を表示してくれる
- その本を記録しておいて、あとから参照できる
という機能を搭載した「ほんめも!」というLINE Botを作ってみたいと思います。
0. この記事で作るもの
この記事では、オウム返しするLINE Botを作っていきます。
プログラムの流れは下記の通りです。
1. プロジェクトを準備する
まずはプロジェクトの雛形を作りましょう。既に雛形がある場合や、既存のコードに組み込む場合は次の章に進んでください。
ターミナル$ bundle init
bundle init
コマンドでGemfileを生成します。
そして、下記のgemを追記していきましょう。Gemfile# 中略 gem "sinatra" gem "sinatra-contrib" gem "dotenv" gem "line-bot-api"記述が完了したら
bundle
コマンドでインストールしましょう。ターミナル$ bundle
続いて、コアのファイルを作っていきます。今回は
app.rb
というファイル名にします。app.rbrequire 'bundler/setup' Bundler.require require 'sinatra/reloader' if development? get '/' do "Hello world!" endさて、実行して動作テストをしてみましょう。
ターミナル$ ruby app.rb -o 0.0.0.0とコマンドを入力して、プログラムを起動します。
Sinatraのデフォルトのポート番号は4567なので、http://localhost:4567/にアクセスしてみましょう。
「Hello world!」と表示されれば、Sinatraの環境構築は完了です!(スクリーンショットのポート番号が違うのはDockerを噛ませている為なのであまり気にしないでください><)2. LINE Developersに登録しよう
2_1. ログインしよう
https://developers.line.biz/ja/
にアクセスしてログインしましょう。普段LINEを使っていればQRコードでログイン出来ます。ログインすると下のような画面に移動するはずです。とりあえず右下のボタンから言語を変更出来るので、日本語にしておくと楽です。
2_2. プロバイダーとチャネル?
LINE Botの開発をすると必ず「プロバイダー」と「チャネル」という単語が出てきます。
これらは、ざっくり説明すると
- プロバイダー: 開発者アカウント
- チャネル: Botアカウント
という意味になります。
プロバイダーは会社や個人、開発者のグループ等の単位で作成し、チャネルは1つのプロバイダーに属します。2_3. プロバイダーを作ろう
というわけで、まずはプロバイダーを作成しましょう。
「新規プロバイダー作成」をクリックすると、プロバイダー名を入力する画面が出てくるので、好きなプロバイダー名を入力しましょう。ここで入力したプロバイダー名はBotの作者として 公開 されます。本名等は控えたほうが良いでしょう。
作成ボタンを押すとプロバイダーが作成されます。2_4. チャネルを作ろう
続いて、Botのアカウントとなるチャネルを作りましょう。
Botで使うチャネルは「Messaging API」なので、Messaging APIをクリックします。新しく作るチャネルの設定画面が出てきます。必要な内容を埋めていきましょう。
項目 内容 チャネルの種類 Messaging API プロバイダー 先程作ったもの チャネルアイコン 今は設定しなくてOK チャネル名 Bot名 チャネル説明 Botの説明 大業種 Botのジャンル 小業種 Botの詳細ジャンル メールアドレス Botに関する連絡が来るメールアドレス。連絡が受けられるメールアドレスにしておくと良いです。 プライバシーポリシーURL 今は設定しなくてOK サービス利用規約URL 今は設定しなくてOK 必要な規約に同意した後、作成ボタンを押すとチャネルが作成されます。
3. Botに必要なキーを.envファイルに書き込もう
Botに必要なチャネルの準備が出来たので、キーやシークレットを.envファイルに書き込んでいきましょう。
プログラム内に直接書き込むことも出来ますが、セキュリティの事を考えるとあまりよろしくありません。.envLINE_CHANNEL_ID=xxxxxxxxxx LINE_CHANNEL_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx LINE_CHANNEL_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxチャネルIDとチャネルシークレットは「チャネル基本設定」
チャネルトークンは「Messaging API設定」の中に「チャネルアクセストークン(長期)」という名前で記載されています。
表示されていない場合は発行ボタンを押して発行しましょう。プログラム内で.envに記述した変数が利用出来るようにするためDotenv.loadをrequireの下に書いておきます。
app.rbrequire 'bundler/setup' Bundler.require require 'sinatra/reloader' if development? Dotenv.load # <- 追記 get '/' do "Hello world!" end4. Webhookを受け付けるようにしよう
4_1 Webhookって何?
Webhookとは、相手のサービス(この記事ではLINE)でイベントが発生したら、事前に設定しておいたURLにイベントの内容をPOSTしてもらう仕組みのことです。
例えば、自分の作ったサービスの
/callback
というURLをLINEのチャネルに設定しておけば、チャネルにメッセージが来た時に/callback
にメッセージの内容をPOSTしてもらう事が出来ます。ただし、LINEのサーバーからアクセス出来るURLである必要があるので、サービスを実際に公開していないときちんと動作しません。例えば開発中のlocalhost:4567は自分のPCからしかアクセス出来ないURLなので、
localhost:4567/callback
を設定した状態でメッセージが来てもlocalhost:4567/callback
は呼び出されません。この仕様があるため、LINE Botを開発する時は基本的に毎回デプロイする必要があります。
ポート開放をすればいちいちデプロイする手間は省けますが、セキュリティ上のリスクもあるのでここでは紹介しません。4_2 Webhookを受け取って返信しよう
基本的にGitHubのREADMEと一緒ですが、画像の処理を抜いてシンプルなコードにしています。
https://github.com/line/line-bot-sdk-rubyapp.rbrequire 'bundler/setup' Bundler.require require 'sinatra/reloader' if development? Dotenv.load # ====== 追記ここから ====== def client @client ||= Line::Bot::Client.new { |config| config.channel_id = ENV["LINE_CHANNEL_ID"] config.channel_secret = ENV["LINE_CHANNEL_SECRET"] config.channel_token = ENV["LINE_CHANNEL_TOKEN"] } end post '/callback' do body = request.body.read signature = request.env['HTTP_X_LINE_SIGNATURE'] unless client.validate_signature(body, signature) error 400 do 'Bad Request' end end events = client.parse_events_from(body) events.each do |event| if event.is_a?(Line::Bot::Event::Message) if event.type === Line::Bot::Event::MessageType::Text message = { type: 'text', text: event.message['text'] } client.reply_message(event['replyToken'], message) end end end "OK" end # ====== 追記ここまで ====== get '/' do "Hello wolrd!" endコードの解説をこの下でしていきます。
4_3 コード解説
def client @client ||= Line::Bot::Client.new { |config| config.channel_id = ENV["LINE_CHANNEL_ID"] config.channel_secret = ENV["LINE_CHANNEL_SECRET"] config.channel_token = ENV["LINE_CHANNEL_TOKEN"] } endこれはLINE Botを操作するための「client」を使えるようにするためのコードです(これは
line-bot-api
の機能です)。
Line::Bot::Client.new
でclientを作る事が出来ますが、1つのサービスで1つのclientがあれば十分なのでこのような実装になっています。
変数名の頭に@を付ける & ||=演算子を使う事で、@clientが空の時はLine::Bot::Client.new
してそれを渡す。@clientが既に入ってる時はそれを渡す。という処理を実現しています。post '/callback' do body = request.body.read signature = request.env['HTTP_X_LINE_SIGNATURE'] unless client.validate_signature(body, signature) error 400 do 'Bad Request' end end
post '/callback' do
のブロックは少し長いので分けて説明します。
body = request.body.read
は送られてきたデータをbody変数に代入しているだけです。signature以降では、送られてきたデータが本当にLINEサーバからのものかをチェックしています。
LINEサーバから送信されるデータには必ず「HTTP_X_LINE_SIGNATURE」というものが含まれており、その中身を見るとLINEサーバから送られてきたデータかどうかを確認する事が出来ます。
LINEサーバかどうかの確認はline-bot-api
に実装されており、先程作ったclientを通して利用する事が出来ます。
確認の処理はclient.validate_signature(body, signature)
という部分で実行されています。悪意のある人がLINEサーバをなりすましてメッセージを送り込んできていないかをチェックするという重要なコードです。
events = client.parse_events_from(body) events.each do |event| if event.is_a?(Line::Bot::Event::Message) if event.type === Line::Bot::Event::MessageType::Text message = { type: 'text', text: event.message['text'] } client.reply_message(event['replyToken'], message) end end end "OK" end
events = client.parse_events_from(body)
では送られてきたデータをrubyで扱いやすい形に変換してもらっています。
変換した結果はeventsという名前からも分かるように、イベントの配列になります。
events.each do |event|
では複数あるイベントを1件ずつ処理しています。これは一度に複数のイベントが同時に送信される事がある為です。
if event.is_a?(Line::Bot::Event::Message)
ではイベントの種類がMessageかどうかを確認しています。メッセージ以外のイベントには、「友だち追加」や「ブロック解除」などがあります。
if event.type === Line::Bot::Event::MessageType::Text
ではメッセージの種類がテキストであることを確認しています。テキスト以外のメッセージの種類には、画像や動画、スタンプなどがあります。つまり、上から4行までのコードで「送信されたデータを解析して、テキストメッセージのみを絞り込む」という処理をしています。
続いてif文の中のコードを見てみましょう
message = { type: 'text', text: event.message['text'] } client.reply_message(event['replyToken'], message)上4行でLINEサーバに送るメッセージを組み立てて、最後の1行で返信を送信しています。
event['replyToken']はイベントに含まれている返信用のトークンです。
最後に
"OK"
と書いていますが、処理が正常に成功したら正しいレスポンスを返す必要があるというLINE Bot APIのルールに則ったものです。何を返してもOKです。4_4 デプロイしよう
さあ、コードは完成したので実行してみましょう!と言いたい所ですが、先程解説したとおり残念ながらローカルでの実行では動作しません。
なので、今回はHerokuへデプロイすることにします。herokuへのデプロイの詳細は割愛しますが、Procfileだけ作成しておきましょう。
Procfileweb: bundle exec ruby app.rb -o 0.0.0.0 -p $PORTターミナル$ git init $ git add -A $ git commit -m "first commit" $ heroku create $ git push heroku masterデプロイできたらアプリケーションを開いてみましょう。「Hello wolrd!」と表示されればデプロイ完了です!
4_5 Webhookを設定しよう
LINE Developersのサイトに行きチャネル設定の画面に移動しましょう。
「Messaging API設定」を開き、「Webhook URL」の編集をクリックします。
URLを入力する枠が出てくるので、先程デプロイしたURL+/callback
と入力して更新ボタンを押しましょう。例えばデプロイしたURLが
https://xxxxxxx-yyyyyy-zzzzz.herokuapp.com
だったら
https://xxxxxxx-yyyyyy-zzzzz.herokuapp.com/callback`
となります。その後、Webhookの利用にチェックを入れておきましょう。
検証ボタンを押すとサーバーの動作チェックが出来ます。成功と表示されれば問題ありません。
また、現時点では自動応答メッセージが有効になっているためBotのメッセージが送信出来ません。なので、自動応答を無効にします。
Messaging API設定の中の「応答メッセージ」の編集ボタンをクリックします。
すると、応答設定というページが表示されるので、下にある詳細設定の
- 応答メッセージを オフ に
- Webhookを オン に
設定します。
これでWebhookを利用する準備が出来ました。
5. 試してみよう
さて、準備は全て整いました!Botを友だちに追加して実際にメッセージを送信してみましょう!
「Messaging API設定」の中にQRコードがあるので、これをLINEで読み込んでみましょう。
作ったBotと友達になれるはずです。友達になれたら何かメッセージを送信してみましょう。
送ったメッセージと同じメッセージが帰ってきたら成功です!おつかれさまでした!6. まとめ
この記事では送られてきたメッセージをオウム返しするBotを作りました。
とてもシンプルなものですが、Bot作りに欠かせない大切なコードが沢山含まれているのでよく理解しておくことをおすすめします!
- 投稿日:2020-07-12T02:58:36+09:00
all_month が気になって ActiveSupport DateAndTime::Calculations を読んでみた
これ何
先輩にレビュー中に、 all_month 使った方がいいんじゃない?と言われたので、気になって調べてみた。
ActiveSupport の DateAndTime::Calculations に定義されていたので、周りのmethodも読んでみる範囲指定のmethod
all_monthの内部実装を眺めてみた。rails/railsの内部実装だが、読めるコードでわかる。
def all_month beginning_of_month..end_of_month endref: activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
同じファイルの中を見てみるとall_month 以外にも all_xxx methodが存在した。対象範囲が変わるだけで、使えそうなmethodが多い。
- all_day
- all_month
- all_quarter:3ヶ月ごとの日時情報が取れる
- all_week:週の開始日を決めることができる
- all_year
気づき 1. future?, past?
未来・過去かを判定するmethodが存在する!再定義しなくても、rails/railsであるものを使える!!気づき。
Date.yesterday.past? => true Date.yesterday.future? => false Date.tomorrow.future? => true Date.tomorrow.past? => false気づき 2. all_xxxと同様にnext_xxx, prev_xxxも存在する
next_xxx, prev_xxx が定義されている。 prev_xxx は last_xxx にaliasされている。
Date.current => Sun, 11 Jul 2020 Date.current.next_week => Mon, 13 Jul 2020 Date.current.prev_week => Mon, 29 Jun 2020 Date.current.last_week => Mon, 29 Jun 2020参考文献