20210503のRubyに関する記事は17件です。

RubyでServerless Frameworkを構築するための覚書

Ruby使ってさっくりlambda functionのAPIを立ち上げるための覚書 準備 Serverless Framework のインストール $ npm install -g serverless テンプレートの展開 $ serverless create -t aws-ruby rbenvを利用しているのでRubyのバージョンを指定 $ rbenv local 2.7.3 API Gatewayを生やしてHTTPリクエストを受け付けるように修正 # serverless.yml functions: handler: handler: handler.webhook events: - httpApi: path: /webhook method: get デプロイ $ serverless deploy -v 環境変数 dotenv 開発をする上でdotenvを利用 step1: serverless-dotenv-pluginをインストール $ npm i -D serverless-dotenv-plugin .envファイルの修正 SAMPLE_ENV=hogehoge serverless.ymlの修正 frameworkVersion: '2' useDotenv: true ... functions: webhook: handler: handler.hello events: - httpApi: path: /hello method: get environment: SAMPLE_ENV: ${env:SAMPLE_ENV} 参考 Building an API with Ruby and the Serverless Framework
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsでGemを使用せずにハッシュタグ検索機能を実装

はじめに 2020年3月より某プログラミングスクールにてRailsを学び同8月よりRailsエンジニアとして勤務しております。 2020年の6月に学習の成果というところで、下記の記事を執筆させて頂いたところ、 想像以上の閲覧数、LGTMを頂きました。本当にありがとうございます。 しかしながら読み返してみると非常にわかりづらい、コードもぐちゃぐちゃ、今の自分でもよくわからないと感じまして、 今回機能を作り直して改めて作成の方法を皆さんと考えていければ良いなと思い、 ハッシュタグ機能の実装方法の記事を書きます。 ※上記記事と実装方法がかなり異なります。 この記事での目的 ・Gemを使わずにハッシュタグ機能を実装する。 ・Arrayのメソッドの使用に慣れる。 ・Hashの使用に慣れる。 この記事ではやらないこと ・modelでのhas_many、belongs_toの使用。 ・migration_fileでの明示的(referenceの使用など)な外部キーの設定。 ・Arrayメソッドの詳しい解説(こちらは調べてください。。) ・イケてるデザインの実装。 前提としてお伝えしておきたいこと 休みに概ね1日で実装をしたので高度な設計などはしてません。 一応こういう考え方で実装できますよ。というところをお伝えできればと思っております。 それではやっていきましょう。 実行環境 ・Ruby version 2.5.7 ・Rails version 5.2.4.4 ・DB sqlite3 完成図 投稿の際に#をつけて投稿することで、 #から始まる文字列がハッシュタグとして保存される。 また、投稿本文の中に存在している#から始まる文字列は表示されない。 ハッシュタグはリンクとなっており、クリックをすることでそのハッシュタグが埋め込まれた投稿一覧を取得できる。 ※みなさんが想像するハッシュタグ機能ですのでご安心を。 posts/new posts/index hashtags/show hashtags/index 作成するファイル一覧 Model ・post.rb #投稿保存用のモデル ・post_hashtag.rb #投稿とハッシュタグの中間テーブル用のモデル ・hashtag.rb #ハッシュタグ保存用のモデル 上記作成用のマイグレーションファイル Controller ・posts_controller.rb #投稿全般のアクションを行います。 ・hashtags_controller.rb #ハッシュタグに紐づいた投稿を表示、ハッシュタグの一覧を表示など。 外部メソッドファイル  ・controllers/concerns/hashtag_methods.rb #自作メソッドが多いため切り分けて作成しました。 Views ・posts/index #投稿一覧 ・posts/new #投稿ページ ・posts/edit #投稿編集ページ ・posts/show #投稿詳細ページ ・hashtags/index #ハッシュタグ一覧ページ ・hasgtags/show #ハッシュタグに紐づいた投稿一覧ページ Routes routes.rbにて下記を記載 config/routes.rb resources :posts #postsコントローラー作成後記入 resources :hashtags, only: [:index, :show] #hashtagsコントローラー作成後記入 ①Modelの作成 post.rb(投稿テーブル) app/models/post.rb class Post < ApplicationRecord validates :title, presence: true validates :caption, presence: true attachment :image #gem refileを使用しているため記入。ハッシュタグ実装には関係ない。  end マイグレーションファイル(posts) db/migrate/create_posts.rb class CreatePosts < ActiveRecord::Migration[5.2] def change create_table :posts do |t| t.integer :user_id t.string :title #投稿のタイトルを入力 t.string :caption #投稿の内容を入力。ここにハッシュタグが入力される前提。 t.string :image_id #画像投稿用 t.timestamps end end end 投稿に一応user_idと画像を持たせたいのでこのような構造になっております。 特になくても大丈夫です。 後述しますが、必要になるのはcaptionカラムのみです。  hashtag.rb(ハッシュタグ保存テーブル) app/models/hashtag.rb class Hashtag < ApplicationRecord #特別な設定はしておりませんが念の為バリデーションはかけても良いと思います。 end マイグレーションファイル(hashtag) db/migrate/create_hashtags.rb class CreateHashtags < ActiveRecord::Migration[5.2] def change create_table :hashtags do |t| t.string :name #ハッシュタグが保存されるカラム t.timestamps end end end post_hashtag.rb(中間テーブル) app/models/post_hashtag.rb class PostHashtag < ApplicationRecord validates :post_id, presence: true validates :hashtag_id, presence: true end マイグレーションファイル(post_hashtag) db/migrate/create_post_hashtag.rb class CreatePostHashtags < ActiveRecord::Migration[5.2] def change create_table :post_hashtags do |t| t.integer :post_id t.integer :hashtag_id t.timestamps end end end 冒頭でも書きましたが、has_manyなどは一切使いません。 また、マイグレーションファイルで外部キーなどの細かい設定も一切せずに実装します。 中間テーブルにはpost_idとhashtag_idが保存されます。 多対多のリレーションになるので、中間テーブルは必須です。 一つの投稿に複数のハッシュタグが保存されますし、一つのハッシュタグには複数の投稿が紐づかなければなりません。 例えばPostモデルからHashtagを取得する場合 1、Postのidを判別する。 2、中間テーブルから、1のidが入っているレコードを検索、取得する。 3、2で取得したレコードに含まれるhashtag_idを取得する。 4、3で取得したhashtag_idからHashtagテーブルに対し検索をかけ、レコードを取得する。 このような流れになります。 逆にHashtagモデルからPostを取得する場合 1、Hashtagのidを判別する。 2、中間テーブルから、1のidが入っているレコードを検索、取得する。 3、2で取得したレコードに含まれるpost_idを取得する。 4、3で取得したpost_idからPostテーブルに対し検索をかけ、レコードを取得する。 ざっくりとした中間テーブルの使用方法です。 これより下記にて作成するメソッドでは上記のような流れを意識して作成をしております。 ②コントローラーの作成 posts_controller.rb(投稿) app/controllers/posts_controller.rb class PostsController < ApplicationController include HashtagMethods before_action :authenticate_user! def index @posts = Post.all @hashtags = Hashtag.all @post_hashtags = PostHashtag.all @post_objects = creating_structures(posts: @posts,post_hashtags: @post_hashtags,hashtags: @hashtags) end def new @newpost = Post.new end def show @post = Post.find(params[:id]) related_records = PostHashtag.where(post_id: @post.id).pluck(:hashtag_id) #=> [1,2,3] idのみを配列にして返す hashtags = Hashtag.all @hashtags = hashtags.select{|hashtag| related_records.include?(hashtag.id)} #hashtagテーブルより中間テーブルで取得したidのハッシュタグを取得。配列に。 @display_caption = @post.caption.gsub(/[##][\w\p{Han}ぁ-ヶヲ-゚ー]+/,"") #実際に表示するキャプション。ハッシュタグが文字列のまま表示されてしまうので、#から始まる文字列を""に変換したものをViewにて表示 end def edit @post = Post.find(params[:id]) end def create @newpost = Post.new(post_params) @newpost.user_id = current_user.id hashtag = extract_hashtag(@newpost.caption) @newpost.save! save_hashtag(hashtag,@newpost) redirect_to posts_path end def update @post = Post.find(params[:id]) strong_paramater = post_params post_params["image"] = @post.image_id if strong_paramater["image"].to_s.length <= 2 #ハッシュタグの実装には関係ないです。画像情報が空で渡ってきた場合は前に保存してある画像をセットするというものです。 delete_records_related_to_hashtag(params[:id]) #こちらのメソッドで中間テーブルとハッシュタグのレコードを削除 @post.update(post_params) hashtag = extract_hashtag(@post.caption) #投稿よりハッシュタグを取得 save_hashtag(hashtag,@post) #ハッシュタグの保存 redirect_to posts_path end def destroy post = Post.find_by(id: params[:id]) #削除対象のレコード post.destroy #投稿を削除 delete_records_related_to_hashtag(params[:id]) #中間テーブルとハッシュタグのレコードを削除 redirect_to posts_path end private def post_params params.require(:post).permit(:title, :caption,:image) end end 今回は保存と表示用に多くのメソッドを自作しているので外部ファイルにまとめてから、コントローラーで呼び出しました。 ファイルの最上部で読んでいる外部ファイルはこちらです。 app/controllers/concerns/hashtag_methods.rb module HashtagMethods extend ActiveSupport::Concern #--------------ハッシュタグ抽出処理 create update アクションの中で実行 ---------------- def extract_hashtag(caption) if caption.blank? #例外処理のため。引数が空で渡ってきた場合は処理をしない return end # 入力された文字列の中より、#で始まる文字列を配列にして返す return caption.scan(/[##][\w\p{Han}ぁ-ヶヲ-゚ー]+/) #=> ["#aaa","#bbb"] end #--------------ハッシュタグ保存処理 create update アクションの中で実行 ---------------- def save_hashtag(hashtag_array,post_instance) if hashtag_array.blank? #ハッシュタグを付けずに投稿された時、下のメソッドを実行させないようにする。 return end hashtag_array.uniq.map do |hashtag| # ハッシュタグは先頭の#を外し、小文字にして保存 tag = Hashtag.find_or_create_by(name: hashtag.downcase.delete('#')) #-------中間テーブルへの保存処理-------- post_hashtag = PostHashtag.new #中間テーブルのインスタンスを作成 post_hashtag.post_id = post_instance.id post_hashtag.hashtag_id = tag.id post_hashtag.save! end end #---------ハッシュタグの情報をPostオブジェクトに含めるメソッド------------ def creating_structures(posts: "",post_hashtags: "",hashtags: "") #引数として必要なのはPostのデータ、中間テーブルの全データ、ハッシュタグの全てのデータです。 #このメソッドはPostのActiveRecordインスタンスをハッシュに変換し、更に一つ一つのオブジェクトに対し、idに紐づくハッシュタグを配列として格納するメソッドです。 array = [] #最終戻り値用 posts.each do |post| hashtag = [] #中間テーブルのID情報から探したハッシュタグを格納するための配列 post_hash = post.attributes #ActiveRecordインスタンスをハッシュに変換 { key => val, key=> val} related_hashtag_records = post_hashtags.select{|ph| ph.post_id == post.id } #中間テーブルより投稿idが一致するレコードを取り出す related_hashtag_records.each do |record| hashtag << hashtags.detect{ |hashtag| hashtag.id == record.hashtag_id } #上記レコードをもとにハッシュタグを検索し、配列に格納 end post_hash["hashtags"] = hashtag #投稿一つ一つのデータに['hashtags']のkeyを追加、そこにハッシュタグのデータを格納する array << post_hash #=> [{"id"=>1, "title"=>"aaaa", "caption"=>"#aaaa", "created_at"=>Sun, 02 May 2021 15:13:42 UTC +00:00, "updated_at"=>Sun, 02 May 2021 15:13:42 UTC +00:00, "user_id"=>1, "image_id"=>"e347a197a5c2e6466db2d5b1673792c0a7b3a37460b1dea00f36b8b366a6", "hashtag"=>[#<Hashtag id: 1, name: "aaaa", created_at: "2021-05-02 15:13:42", updated_at: "2021-05-02 15:13:42">}] end return array end #---------ハッシュタグの情報をハッシュタグテーブルと中間テーブルから削除するメソッド------------ def delete_records_related_to_hashtag(post_id) relationship_records = PostHashtag.where(post_id: post_id) #中間テーブルのレコード if relationship_records #中間テーブルにレコードが保存されていれば relationship_records.each do |record| record.destroy #中間テーブルのレコードを削除する end end all_hashtags = Hashtag.all all_related_records = PostHashtag.all all_hashtags.each do |hashtag| #ハッシュタグに紐づくレコードが中間テーブルに保存されていなければ、ハッシュタグを削除する if all_related_records.none?{ |record| hashtag.id == record.hashtag_id } hashtag.destroy end end end end 一応いたるところにコメントを書いてはいるので、 適宜質問していただければと思います。 こちら外部ファイルを作成して、コントローラーの一番上にてincludeすることにより外部ファイルのメソッドが呼び出せるようになります。 各メソッドの目的をざっくりと下記にまとめます。 extract_hashtag(caption) 引数(caption)に入ってきた文字列から、先頭が#で始まる文字列を配列にして返すメソッドです。 save_hashtag(hashtag_array, post_instance) ハッシュタグをハッシュタグテーブルに保存する、そして中間テーブルへの保存処理を行っております。 引数で渡ってきたハッシュタグの配列を、重複しないようにハッシュタグテーブルに保存の後、 引数で渡ってきたpost_instance(Post.new)のようなオブジェクトのidをハッシュタグのidとセットにして中間テーブルに保存します。 ですので保存の処理は、 ①extract_hashtagにてハッシュタグを抽出。②save_hashtagの引数に①のハッシュタグと①の時に作っているであろうPost.newの値を渡す→保存のような流れになります。 creating_structures(posts: "",post_hashtags: "",hashtags: "") 投稿と一緒にハッシュタグを表示するために作成したメソッドです。 「構造体を作成する」という意味です。 indexやshowにて投稿を表示したとして、中間テーブルのそのまた向こうにいるハッシュタグをどう取得するかを考えると結構難しいと言いますか、なんか大変そうな気がします。 それこそさっき述べた下記の流れを1オブジェクトに対して毎回行わなければなりません。 1、Postのidを判別する。 2、中間テーブルから、1のidが入っているレコードを検索、取得する。 3、2で取得したレコードに含まれるhashtag_idを取得する。 4、3で取得したhashtag_idからHashtagテーブルに対し検索をかけ、レコードを取得する。 クエリもとんでもない量になりますし、なにより面倒ということで、Post.allの一つ一つのレコードに対してハッシュタグを入れ込んでしまう(合体させてしませえば)いいという考えから作成したメソッドになります。 ActiveRecordInstance(Post.find(params[:id])とかで返ってくるオブジェクト)を一度ハッシュにして、一つ一つのレコードというか、値に対して、新しくhashtagsというkeyを与えます。 そしてそのkeyの中に中間テーブル経由で取得したハッシュタグを入れ込み、最後は大きな配列にまとめて返します。 (post_hash["hashtags"] = hashtag) array << post_hash このarray << post_hashのコメントにあるように、hashtagsというkeyの中に配列を埋め込みます。 delete_records_related_to_hashtag(post_id) コメントにあるように投稿を削除した時に関連するレコードをまとめて削除するメソッドです。 モデルでよく記載されている dependent destoryを手作業でやったイメージです。 投稿を消す→中間テーブルのレコードを消す その後、ハッシュタグテーブルのレコード一つ一つが中間テーブルに保存されているかを調べます。 もし中間テーブルに保存されていなければ、それは関連する投稿がないということですのでハッシュタグも削除します。 showアクションにて使用している.pluckというのはActiveRecordインスタンスに使用するメソッドです。 こちらは引数にシンボルで与えた値(カラム名)を配列にして返すというメソッドです。 詳しくは下記記事にて 先に投稿関連のViewを app/views/posts/index.html.erb <h2>ハッシュタグが埋め込まれている投稿一覧</h2> <div style = "display: flex; flex-wrap: wrap;"> <% @post_objects.each_with_index do |post,i| %> <% display_caption = post["caption"].gsub(/[##][\w\p{Han}ぁ-ヶヲ-゚ー]+/,"") %> <div style="border: 1px solid black;margin: 2%;padding: 3%;width: 25%;"> <%= attachment_image_tag @posts[i],:image,style: 'width: 40%;' %> <div style="border-bottom: 1px solid"> <p style="color: red">title</p> <%= post["title"] %> </div> <div> <p style="color: red">caption</p> <p> <%= display_caption %><br> <% if post["hashtags"].present? %> <% post["hashtags"].each do |hashtag| %> <%= link_to "##{hashtag.name}", hashtag_path(hashtag.id) %> <% end %> <% end %> <p> </div> <%= link_to "詳細", post_path(id: post["id"]) %> <%= link_to "編集", edit_post_path(id: post["id"]) %> <%= link_to "削除", post_path(id: post["id"]),method: :delete %> </div> <% end %> </div> @post_objectsには先ほど外部メソッド(creating_structures)で作ったオブジェクトが格納されております。 こちらはハッシュの形でデータが渡ってきますのでtitleもpost.titleではなくpost["title"]という記載になるところに注意が必要です。 display_captionというのはcaptionの中に保存されてしまっているハッシュタグを非表示にしたものです。 gsubを使用し第一引数にマッチするものを第二引数(””)に変換しております。 またattatchment_image_tagを使用する際にはどうやらハッシュの情報ではダメらしく、やむを得ずハッシュに変換する前のActiveRecordのインスタンスを使用しております。 app/views/posts/show.html.erb <h2>投稿詳細</h2> <div style="border: 1px solid black;margin: 2% 0;padding: 3%;"> <%= attachment_image_tag @post,:image,style: 'width: 40%;' %> <div style="border-bottom: 1px solid"> <p style="color: red">title</p> <%= @post.title %> </div> <div> <p style="color: red">caption</p> <p> <%= @display_caption %><br> <% if @hashtags.present? %> <% @hashtags.each do |hashtag| %> <%= link_to "##{hashtag.name}", hashtag_path(hashtag.id) %> <% end %> <% end %> <p> </div> <div> <%= link_to "編集", edit_post_path(id: @post.id) %> <%= link_to "削除", post_path(id: @post.id),method: :delete %> </div> </div> 投稿詳細ページはindexと違い、ハッシュの形を使用しておりませんので従来のpost.titleという形で情報を出力しております。 app/views/posts/new.html.erb <h2>投稿ページです</h2> <%= form_with model:@newpost, local: true do |f| %> <div> <p>画像を選択</p> <%= f.attachment_field :image %> <p>タイトル入力</p> <%= f.text_field :title %> <br> <p>投稿内容入力</p> <%= f.text_area :caption, size:"55x12" %> <%= f.submit "新規投稿" ,class:'postimage-new-button' %> </div> <% end %> 手抜きですみません・・・ app/views/posts/edit.html.erb <h1>投稿の編集</h1> <%= form_with model:@post, local: true do |f| %> <div> <p>画像を選択</p> <%= f.attachment_field :image %> <p>タイトル入力</p> <%= f.text_field :title %> <br> <p>投稿内容入力</p> <%= f.text_area :caption, size:"55x12" %> <%= f.submit "更新" ,class:'postimage-new-button' %> </div> <% end %> こちらもこれといって特記事項はありません。 続いてhashtag_controller app/controllers/hashtags_controller.rb class HashtagsController < ApplicationController include HashtagMethods def index hashtags = Hashtag.all.select(:id,:name) #全てのハッシュタグを取得 hashtag_count = PostHashtag.all.group(:hashtag_id).count #中間テーブルのレコードをhashtag_id毎にグループ化し、数を取得 @hashtags = [] hashtags.each_with_index do |hashtag,i| hashtag = hashtag.attributes #ハッシュに変換 hashtag["count"] = hashtag_count[hashtag["id"]] #countというkeyを増やし、中間テーブルの数の情報を格納する @hashtags << hashtag #配列に格納 end if @hashtags.length > 1 @hashtags = @hashtags.sort{ |a,b| b["count"] <=> a["count"]} #表示はハッシュタグが使用されている投稿の多い順にする end end def show post_hashtags = PostHashtag.all relationship_records = post_hashtags.select{ |ph| ph.hashtag_id == params[:id].to_i}.map(&:post_id) #中間テーブルの全レコードより、該当ハッシュタグIDが含まれるものを取得→post_idを配列に格納 #=> [1,3] all_posts = Post.all @posts = all_posts.select{ |post| relationship_records.include?(post.id)} #中間テーブルの情報が含まれるPostのレコードを取得する @post_objects = creating_structures(posts: @posts,post_hashtags: post_hashtags ,hashtags: Hashtag.all) #取得したレコードをハッシュに変換し、ハッシュタグを一つ一つのハッシュに格納する。 end end こちらでもincludeにて外部ファイルを呼び出して、hashtag_methods.rbのメソッドを使用できるようにしております。 Views(hashtag) app/views/hashtags/index.html.erb <h2>ハッシュタグ一覧</h2> <% @hashtags.each do |hashtag| %> <% name = hashtag["name"] count = hashtag["count"] id = hashtag["id"] %> <p> <%= link_to "##{name}(#{count})", hashtag_path(id) %> </p> <% end %> 最低限の機能です。 紐づく投稿が何件なのかが()の中に表示されています。 冗長なコードになるのもあれなので、name、count、idという変数に情報を格納しています。 app/views/hashtags/show.html.erb <h2>ハッシュタグが埋め込まれている投稿一覧</h2> <div style = "display: flex; flex-wrap: wrap;"> <% @post_objects.each_with_index do |post,i| %> <% display_caption = post["caption"].gsub(/[##][\w\p{Han}ぁ-ヶヲ-゚ー]+/,"") %> <div style="border: 1px solid black;margin: 2%;padding: 3%;width: 25%;"> <%= attachment_image_tag @posts[i],:image,style: 'width: 40%;' %> <div style="border-bottom: 1px solid"> <p style="color: red">title</p> <%= post["title"] %> </div> <div> <p style="color: red">caption</p> <p> <%= display_caption %><br> <% if post["hashtags"].present? %> <% post["hashtags"].each do |hashtag| %> <%= link_to "##{hashtag.name}", hashtag_path(hashtag.id) %> <% end %> <% end %> <p> </div> <%= link_to "詳細", post_path(id: post["id"]) %> <%= link_to "編集", edit_post_path(id: post["id"]) %> <%= link_to "削除", post_path(id: post["id"]),method: :delete %> </div> <% end %> </div> posts/indexと同じコードです。 これにて完成です。 まとめ 表示をしたいオブジェクトは自分の使いやすい形で作成してしまおう!! 以上です! 終わりに 長々と読んでくださりありがとうございました。 僕が当初ハッシュタグ機能を実装した際にはRails(フレームワーク)のヘルパーを多く使用したことで、できたけどよく分からないというような状態になってしまったのもあり、今回は地道な実装の方法を取ってみました。 自由度の高いRubyですので、使いやすいデータを自分で作ることも容易いと感じます。 かなり高速で作ったので見つけていないバグがあったり、ここ無駄だよねってところは是非教えてください。 また質問もお待ちしております。 無事にハッシュタグが実装できることをお祈りしています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】フォロー機能実装(routes、モデル編)

完成GIF フォローする、フォローを外すを表示 フォロー中と、フォロワーの数を表示 非同期通信で実装 フォロー機能実装には、多対多のリレーションとなる。 複雑な部分も多いのでゆっくりまとめていきます。 1.テーブル作成 フォロー機能では、User対Userとなる。 つまり、多対多です。 Relationshipテーブル カラム データ 説明 id integer PK follower_id integer フォローするユーザのid followed_id integer フォローされるユーザーのid ターミナル $ rails g model Relationship //必要なデータを入れよう $ rails db:migrate 2.Modelの編集 Relationshipモデルに以下の記述をして、Userとのリレーションを繋ぐ。 app/models/relationship.rb belongs_to :followed, class_name: "User" belongs_to :follower, class_name "User" //class_nameを付与する事で、Userを参照するようにする class_nameは一つのモデルに対して、二つのアソシエーションを組む際に使用します。 今回のケースで考えると、followerとfollowedが上記に当てはまります。 次にUserモデルの記述に移ります。 この辺りが少しややこしくなります。 app/models/user.rb class User < ApplicationRecord has_many :reverse_of_relationships, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy //フォローされる側のリレーション has_many :relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy //フォローする側のリレーション has_many :followers, through: :reverse_of_relationships, source: :follower //自分をフォローしている人のデータ has_many :followings, through: :relationships, source: :followed //自分がフォローした人のデータ end foreign_key(外部キー)について has_many :reverse_of_relationships, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy has_many :relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy class_nameをRelationshipとして、どこのテーブルと情報のやり取りをするかを指定。 foreign_key(外部キー)で、どこの情報と繋がるかを指定。 reverse_of_relationshipsはfollowed_id relationshipsはfollower_idとリレーション関係となる。 そもそもPrimary_key(主キー)とforeign_key(外部キー)って? 例) userモデル id(PK) name 1 碇 2 綾波 3 アスカ evaモデル id(PK) user_id(FK) unit 1 2 零号機 2 1 初号機 3 3 弐号機 userモデルとevaモデルにはそれぞれid(PK)がある。 二つのモデルを繋ぐためにevaモデルにはuser_id(FK)があり、関係性を結ぶことが出来る。 先程のフォロー機能でも、FKを指定することでどこの情報と繋ぎたいかを明確にすることができます。 throughとsource has_many :followers, through: :reverse_of_relationships, source: :follower has_many :followings, through: :relationships, source: :followed throughはあるリレーションを他テーブルを通じて実現するために使います。 先ほど作成したリレーションを通じて、sourceで欲しい情報があるカラムへと繋ぎます。 followers => reverse_of_relationships => follower フォロワーデータ一覧 followings => relationships => followed フォローデータ一覧 例えば、user.followerとすることで、userのfollowerデータを簡単に取得することができます。 これで、TwitterやFacebookのようなフォロー、フォロワー一覧を表示させることも出来る訳ですね。 3.routesを編集 resources :users,only:[:index,:show,:edit,:update] do resource :relationships, only:[:create,:destroy] get 'followings', to: 'relationships#followings', as: 'followings' get 'followers', to: 'relationships#followers', as: 'followers' end Relationshipsに:idは必要ないので、resourseで作成。 情報量も多いので、次の記事にControllerとViewの記述を紹介します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsコードリーデイング③ route.rbのgetメソッドを深掘りしてみた 【備忘録】

はじめに MVCにおいてどのように内部処理が行われページ遷移に至るのかフレームワークを使用していると何も気にせずとも開発できてしまいます。 しかしこの基本的な動作の仕組みを理解することで、エンジニアとしての基本的な思考を理解できるのではと考えました。 今回は最も基本的なgetメソッドを見ていきます。 準備 以下のルーティングを作成 rails/config/route.rb Rails.application.routes.draw do binding.pry get 'users' => 'users#index' end 実行 getメソッド rails/actionpack/lib/action_dispatch/routing/mapper.rb module ActionDispatch module Routing class Mapper (省略) module HttpHelpers # Define a route that only recognizes HTTP GET. # For supported arguments, see match[rdoc-ref:Base#match] # # get 'bacon', to: 'food#bacon' def get(*args, &block) map_method(:get, args, &block) end args: {"users"=>"users#index"} block: []  学び① 仮引数の* (*args) 仮引数に*をつけることで余りを全て配列として受け取る事ができる。これを可変長引数と呼ぶ。 https://docs.oracle.com/javase/jp/7/technotes/guides/language/varargs.html 引数の数に柔軟に対応したいときに使えそうです。 学び② ブロック ブロックは「処理の集合体」です。無名関数とかラムダ式とかクロージャのようなものです。 https://qiita.com/genya0407/items/1a34244cba6c3089a317 getに対し処理を追記した場合にblockに処理が入ると思われます。 { |e| e + 1 } こちらのような表記です。 map_methodメソッド rails/actionpack/lib/action_dispatch/routing/mapper.rb module HttpHelpers private def map_method(method, args, &block) options = args.extract_options! options[:via] = method match(*args, options, &block) self end end argsをpopしています。今回要素は1つだけなので、optionsにargsがそのまま代入されます。 map_methodメソッドは、整形してoptionsに getメソッド {"users"=>"users#index"} という2点の情報をまとめる機能を担っています。 rails/actionpack/lib/action_dispatch/routing/mapper.rb module Resources # Matches a URL pattern to one or more routes. # For more information, see match[rdoc-ref:Base#match]. # # match 'path' => 'controller#action', via: :patch # match 'path', to: 'controller#action', via: :post # match 'path', 'otherpath', on: :member, via: :get def match(path, *rest, &block) if rest.empty? && Hash === path options = path path, to = options.find { |name, _value| name.is_a?(String) } raise ArgumentError, "Route path not specified" if path.nil? case to when Symbol options[:action] = to when String if /#/.match?(to) options[:to] = to else options[:controller] = to end else options[:to] = to end options.delete(path) paths = [path] else options = rest.pop || {} paths = [path] + rest end if options.key?(:defaults) defaults(options.delete(:defaults)) { map_match(paths, options, &block) } else map_match(paths, options, &block) end end 学び③ === self === obj -> bool 指定された obj が self かそのサブクラスのインスタンスであるとき真を返します。また、obj が self をインクルードしたクラスか>そのサブクラスのインスタンスである場合にも真を返します。上記のいずれでもない場合に false を返します。 https://docs.ruby-lang.org/ja/latest/method/Module/i/=3d=3d=3d.html モジュール内で型を確認する場合に使えそうです。 obj.kind_of?(self)と同じ機能ということですが、===の方がスッキリかけます。ただobjとselfの並びが逆なことには注意が必要です。 学び④ findメソッドと多重代入の組み合わせ path, to = options.find { |name, _value| name.is_a?(String) } ハッシュのname(keyと呼ぶことの方が多いが)がStringの場合は、pathとtoに代入します。 シンボルで表記されていた場合は、代入されないということになります。 変数を_valueと先頭にアンダーバーを付けているのは何故でしょうか。 こちらは、先頭アンダーバーのもので絞り込むことによって探しやすいというメリットがあるようです。処理内の変数なのでこの表記にする理由はいまいちわかりませんでした。 https://anderson02.com/cs/cs-rules/cs-rules10/ 最後に 今日はここまでです。前処理の部分でこんなに学びがあるとはという感想です。 まだまだ、routingがどのように処理されているかに踏み込めていないため、この部分のコードリーディング継続する予定です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby 問題① ハッシュの基礎について

はじめに こんにちは 3月から学習を始めてアウトプットを兼ねて投稿させていただきます。 問題 ruby.rb puts menu.keys puts menu.values 上記のメソッドを記述した時、 ruby.rb 餃子 炒飯 ラーメン 240 450 650 とターミナルに表示なれるような変数studentを作成するためのコードを考えて下さい。 ヒント ・ハッシュの生成 ・keyとvalueの存在 解答 ruby.rb menu = {餃子:240, 炒飯:450, ラーメン:650,} puts menu.keys puts menu.values 餃子 炒飯 ラーメン 240 450 650 解説 ハッシュとは ハッシュとは「データ」とそれに対応する「名前」のセットを要素として持つ値です。 変数 = {} #hashの宣言 #{}波括弧を使います シンボルとは シンボルは、見た目は文字列のようですが、実際の中身は数値になっている値です。 複数のデータを持つことのできる値という点では、配列と同じです。 しかし、ハッシュは「キー」で管理する事ができます。 試しに配列の場合を記述します。 ruby.rb menu = ["餃子",240,"炒飯",450,"ラーメン",650] puts menu 餃子 240 炒飯 450 ラーメン 650 挙動は似ているようで読みにくい場合があります。 ハッシュとの大きな違いは、値にそれぞれキーを設定できるという事です。 hashの利用について railsでUserモデルでユーザーのデータを取得したと考える事にします。 User = ["name","tom","age",25,"address","tokyo"] puts User[1] #=> tom puts User[3] #=> 25 puts User[5] #=> tokyo 配列だと呼び出す時に[添字]で呼び出すため何を呼び出しているかが見にくいです。 User = {name:"tom", age:25, address:"tokyo"} puts User[:name] #=> tom puts User[:age] #=> 25 puts User[:address] #=> tokyo この記述だと何の値を呼び出しているかが一目でわかります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[解決]Access to XMLHttpRequest at from origin has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

結論 RailsAPIとVue間でデータを共有しようとしたところエラー。API側のCORS設定がうまく行っていなかったことが原因だった。 詳しく エラーのコード Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins '*' resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head] end end CORSが正常に機能したコード Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins 'http://localhost:8080', 'http://localhost:3002/v1/blogs/' resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head] end end originを全て受け入れる(*)にすると、CORSのセキュリティ的にエラーが出るようになっているらしい。だから、クライアント側のoriginを明記すると、CORSが機能する。 参考 https://web.dev/cross-origin-resource-sharing/ https://qiita.com/att55/items/2154a8aad8bf1409db2b https://qiita.com/residenti/items/3a03e5e0268b354284b7
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails 自動整形ツールRubocop

初心者なので、勉強用にのこしております。 Rubocop Rubocop(ルボコップ)はRubyの静的コード解析ツールです。「インデントが揃っていない」「余分な改行・スペースがある」などの指摘をRubyStyleGuideに基づいて行ってくれます。 Rubocopを導入することにより、レビューに掛かる時間を減らし、コードの品質を担保できるようになります。 Rubocopを導入しましょう Gemfileに記述 group :development do gem 'rubocop', require: false end 一番下ではないのに気をつけよう そして bundle install Rubocopの設定を記述するファイルを新規作成します。 touch .rubocop.yml .rubocop.ymlの中身を記述する。 たぶん、会社やチームのきまりで決まっているものがあるとおもわれる。 こちらは、例え AllCops: # 除外するディレクトリ(自動生成されたファイル) # デフォルト設定にある"vendor/**/*"が無効化されないように記述 Exclude: - "vendor/**/*" # rubocop config/default.yml - "db/**/*" - "config/**/*" - "bin/*" - "node_modules/**/*" - "Gemfile" # 1行あたりの文字数をチェックする Layout/LineLength: Max: 130 # 下記ファイルはチェックの対象から外す Exclude: - "Rakefile" - "spec/rails_helper.rb" - "spec/spec_helper.rb" # RSpecは1つのブロックあたりの行数が多くなるため、チェックの除外から外す # ブロック内の行数をチェックする Metrics/BlockLength: Exclude: - "spec/**/*" # Assignment: 変数への代入 # Branch: メソッド呼び出し # Condition: 条件文 # 上記項目をRubocopが計算して基準値を超えると警告を出す(上記頭文字をとって'Abc') Metrics/AbcSize: Max: 50 # メソッドの中身が複雑になっていないか、Rubocopが計算して基準値を超えると警告を出す Metrics/PerceivedComplexity: Max: 8 # 循環的複雑度が高すぎないかをチェック(ifやforなどを1メソッド内で使いすぎている) Metrics/CyclomaticComplexity: Max: 10 # メソッドの行数が多すぎないかをチェック Metrics/MethodLength: Max: 30 # ネストが深すぎないかをチェック(if文のネストもチェック) Metrics/BlockNesting: Max: 5 # クラスの行数をチェック(無効) Metrics/ClassLength: Enabled: false # 空メソッドの場合に、1行のスタイルにしない NG例:def style1; end Style/EmptyMethod: EnforcedStyle: expanded # クラス内にクラスが定義されていないかチェック(無効) Style/ClassAndModuleChildren: Enabled: false # 日本語でのコメントを許可 Style/AsciiComments: Enabled: false # クラスやモジュール定義前に、それらの説明書きがあるかをチェック(無効) Style/Documentation: Enabled: false # %i()構文を使用していないシンボルで構成される配列リテラルをチェック(無効) Style/SymbolArray: Enabled: false # 文字列に値が代入されて変わっていないかチェック(無効) Style/FrozenStringLiteralComment: Enabled: false # メソッドパラメータ名の最小文字数を設定 Naming/MethodParameterName: MinNameLength: 1 文字列 = シングルクォーテーション 式展開や文字列内にシングルクォーテーションがある場合はシングルクォーテーションを使います 変数名とメソッド名 = スネークケースで定義する というデフォルトの標準設定がしてあります。他にもあるそうです。 Rubocopを実行するにはターミナルで以下のコマンドを実行します。 # Rubocopを実行 % bundle exec rubocop チェック内容を自動で修正させることもできます。 # Rubocopを実行し、check内容を自動修正 % bundle exec rubocop -a 以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DoorkeeperのAuthorization Code Flowが導入されている状態からPKCEを導入する手順

これは何? 前回こちら の記事でPKCEのフローをまとめました。 今回は実際にdoorkeeperというgemを使って実装していこうと思います。 doorkeeper gemでPKCEを導入する手順 以前 こちら の記事でdoorkeeper gemでOAuthプロバイダ機能を作成する記事を投稿しました。 今回は前回の状態からPKCEを導入する手順を記載します。 (※ doorkeeper gem のwikiにもPKCEについて記載があるのでご確認お願いします。) (https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-PKCE-flow) 導入手順 # wikiに記載されているように以下のコマンドを実行します。 bundle exec rails generate doorkeeper:pkce # 実行すると以下のmigrationファイルが作成されます。 db/migrate/20210330224805_enable_pkce.rb migrationファイルの中身は以下のようになっており oauth_access_grantsテーブルへのカラム追加となっています。 # frozen_string_literal: true class EnablePkce < ActiveRecord::Migration[6.0] def change add_column :oauth_access_grants, :code_challenge, :string, null: true add_column :oauth_access_grants, :code_challenge_method, :string, null: true end end migrateを実行します。 rails db:migrate RAILS_ENV=development これで oauth_access_grants テーブルへ code_challenge、code_challenge_method を保存することができます。 続いてOAuth Applicationを作成していきます。 http://localhost:3000/oauth/applications へアクセスし[New Application]をクリックします。 アプリケーションの情報を入力して登録します。 今回Client側のアプリのURLは http://localhost:3001 としcallbackURLを http://localhost:3001/pkce_callback として準備しました。 画面に表示されている CLIENT_ID(UID)、CLIENT_SECRET、scopes をClient側のアプリへ設定しておきます。 ここでPKCEの仕組みを以下へ記載します。 1. code_verifier を使って code_challenge_method に合った code_challenge を生成し、code_challenge, code_challenge_method をパラメータへ指定して認可画面をリクエストします。 2. 認可サーバは code_challenge, code_challenge_method を保存し、Clientへ認可コードを返します。 3. Clientは認可コードと code_verifier を使ってアクセストークンを取得します。 4. 認可サーバで受け取った code_verifierを保存してある code_challenge_methodで変換して code_challengeのになるか検証します。 それではPKCEを使うためClientアプリケーションの OAuth2::Clientの部分を実装していきます。 以下は認可画面のURLを作成する処理のサンプルになります。 # 認可画面のURLを作成する処理 def pkce_authorization session[:code_verifier] = SecureRandom.alphanumeric(100) client = OAuth2::Client.new( ENV["CLIENT_ID_PKCE"], ENV["CLIENT_SECRET_PKCE"], site: ENV["SITE"], authorize_url: ENV["AUTHORIZE_URL"], token_url: ENV["TOKEN_URL"] ) code_challenge = Base64.urlsafe_encode64(OpenSSL::Digest::SHA256.digest(session[:code_verifier]), padding: false) authorize_url = client.auth_code.authorize_url( redirect_uri: ENV["CALLBACK_URI_PKCE"], scope: ENV["SCOPE"], code_challenge: code_challenge, code_challenge_method: "S256" ) redirect_to authorize_url end code_verifier の値は、[A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~" からなるランダムな文字列であり、最低43文字、最大128文字の長さが必要となります。 今回は code_verifier = SecureRandom.alphanumeric(100) のように100文字の英数字を設定します。ref:https://tools.ietf.org/html/rfc7636 code_challenge は code_challenge_method によって値が変わります。 code_challenge_method を plain にした場合 code_challenge = code_verifier となります。 code_challenge_method を S256 にした場合 code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) となります。 ref:https://tools.ietf.org/html/rfc7636 これをrubyで実装すると code_challenge = Base64.urlsafe_encode64(OpenSSL::Digest::SHA256.digest(session[:code_verifier]), padding: false) で実装できます。 code_challenge_method は 2種類あるのですが plainは code_verifierと code_challenge が同じ値のため特に plain を選ぶ理由がなければセキュリティリスクを考えて S256 を使用しましょう。 続いて認可コードが返ってきてからアクセストークンを取得する部分のサンプルが以下になります。 # 認可コードよりアクセストークンを取得 def pkce_callback client = OAuth2::Client.new( ENV["CLIENT_ID_PKCE"], ENV["CLIENT_SECRET_PKCE"], site: ENV["SITE"], authorize_url: ENV["AUTHORIZE_URL"], token_url: ENV["TOKEN_URL"] ) code_verifier = session[:code_verifier] session.delete(:code_verifier) @access_token = client.auth_code.get_token( params[:code], redirect_uri: ENV["CALLBACK_URI_PKCE"], code_verifier: code_verifier ) render :callback end 実際に認可画面から認可後、アクセストークンを取得できるか確認します。 認可画面 認可コード取得後、アクセストークン取得 念のため、認可サーバ側で code_challenge, code_challenge_method が保存されているか確認しておきます。 Doorkeeper::AccessGrant.all Doorkeeper::AccessGrant Load (0.3ms) SELECT "oauth_access_grants".* FROM "oauth_access_grants" LIMIT ? [["LIMIT", 11]] => #<ActiveRecord::Relation [#<Doorkeeper::AccessGrant id: 1, resource_owner_id: 1, application_id: 2, token: "ygCPJhHoenE3vfiY1StLqCVBRJWTZfWnZFdOPUcTn7M", expires_in: 600, redirect_uri: "http://localhost:3001/pkce_callback", created_at: "2021-05-03 07:32:25", revoked_at: "2021-05-03 07:32:25", scopes: "public write", code_challenge: "oDJceObZlNuTivuP6EocfJZt2ODCdyjOh5IyUXK_4Rw", code_challenge_method: "S256">]> 正常にcode_challenge, code_challenge_methodが保存されていることが確認できました! 最後に doorkeeper gemを使用していればPKCEの導入は非常に簡単にできました。 Authorization Code Flowとの共存も可能なため実際の業務でもPKCEは導入しやすいと思います!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails] Rspecでテストコードの実装③

はじめに テストコードの実装②をみていない方はこちらからご覧ください。 コントローラの単体テスト 今回はコントローラの単体テストについてまとめていきます。コントローラのテストでは、あるアクションにリクエストを送ったとき、想定通りのレスポンスが生成されるかどうかを確かめることが主な役割です。RSpecの中でもRequest Specと呼ばれる手法を利用します。 なお、コントローラーのテストコードは、結合テストコードに記述する内容と、同じような責務を果たすことが多いです。よって、今回は簡単にまとめるのみといたします。 Request Spec(参考) RSpecが提供している、コントローラーのテストコードを書くために特化した手法です。RSpecの導入が完了していれば使用できます。 テストコードの具体的な実装 まずはファイルの生成をします。 rails g rspec:request tweets 次に生成されたファイルに以下のようにテスト項目を入力してください。(テスト項目は完璧ではありません、あくまで一例です) なお今回、すでにある投稿に対してテストを行いたいので、buildではなく createを使用しています。 spec/requests/tweets_spec.rb require 'rails_helper' describe TweetsController, type: :request do before do @tweet = FactoryBot.create(:tweet) end describe 'GET #index' do it 'indexアクションにリクエストすると正常にレスポンスが返ってくる' do     get tweet_path(@tweet)  expect(response.status).to eq 200 end it 'indexアクションにリクエストするとレスポンスに投稿済みのツイートのテキストが存在する' do     get tweet_path(@tweet) expect(response.body).to include(@tweet.text) end #一部省略 end end 一つづつ見ていきます。 get get tweet_pathのように、どこのパスにリクエストを送るかを記述します。 response リクエストに対するレスポンスのこと。 このレスポンスで取得できる情報に、想定する内容が含まれているかを確認することで、テストコードを書くことができます。 response.status そのレスポンスのHTTPステータスコードを出力できます。 response.body response.bodyと記述すると、ブラウザに表示されるHTMLの情報を抜き出すことができます。 コントローラの単体テストでは、リクエストに対して、どんなレスポンスをするかの確認をしていることを意識しながら見るとわかりやすいのではないでしょうか おわりに お疲れ様です。 今回はかなり簡潔にコントローラの単体手薄とについてまとめましたので、「え、これだけ?」と不安になる方もいるでしょう。しかし心配無用です!! 先に書いた通り、結合テストでしっかり確認していくので問題はありません!!!! 次回をぜひ楽しみにしていてください! つづき Rspecでテストコードの実装④
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

セッターとゲッターの使い方

セッターとゲッターの役割 セッター  クラス外からインスタンス変数の値を変更できるインスタンスメソッド ゲッター  クラス外からインスタンス変数の値を出力できるインスタンスメソッド 使用例 class People # セッター('name='までがメソッド名) def name=(name) @name = name # 2. メソッドの引数を@nameに入れる end # ゲッター def name @name # 3. @nameを出力する end # ゲッターの応用 def introduction puts "私の名前は#{@name}です。" # 1. メソッドを呼ぶ際に@nameを任意に変更したい end end # 1〜3の過程で、セッターメソッドに渡した引数がインスタンス変数に入るようになった。 user = People.new user.name=("太郎") # セッターを使い、"太郎"が@nameに入った puts user.name # ゲッターを呼ぶと@nameが出力される #=> 太郎 user.introduction #=> 私の名前は太郎です。 user.name = "花子" # セッターで@nameの値を"花子"に設定した user.introduction #=> 私の名前は花子です。 まとめ  インスタンス変数の「メソッド間で同じ値を共有する」という性質と  インスタンスメソッドの「クラス外でも使用できる」という性質を組み合わせることで  「クラス外でもインスタンス変数の値を変更・出力できる」という使い方をしたものが、  ゲッターとセッターである。 簡単に使う セッターを定義 attr_writer :メソッド名 ゲッターを定義 attr_reader :メソッド名 ゲッターとセッターをまとめて定義 attr_accessor :メソッド名
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsのみで日付からデータを表示する方法

記事の概要 Webサービス上で日付を入力して「送信」のようなボタンを押すと、その日付に応じた結果が表示されることはよくあるものと思います。これをRailsで実装する手順について解説していきたいと思います。 バージョン ・ruby 2.6.5 ・rails 6.0.3.4 ・mysql 14.14 体重や体脂肪をデータベースに保存してそれを表示するアプリケーションを例にとって考えてみます。 前提として、事前にデータベースに日付・体重・体脂肪・体調の情報が保存されているものとします。 まず、記録を保存する機能として、Body_Record というモデルを用意しています。 app>models>body_record.rb class BodyRecord < ApplicationRecord belongs_to :user, optional: true with_options presence: true do validates :date #日付必須 validates :body_weight #体重必須 validates :fat #体脂肪率必須 end 続いてビューは以下の通りです。 ビューファイルの中身は以下のようになっています(CSSは割愛します) app>views>body_records>index.html.erb <h3 class="input-nav">体重・体脂肪記録を確認する</h1> <% unless @body_record.empty? %> <div class="action-box"> <%= form_tag(search_body_records_path, method: :post) do %> <p>見たい日付を選択してください<input type="date" name="body_record[date]" id="body_record_date"> <input type="submit" value="確認する", class="btn btn-success"></p> <% end %> </div> #テーブル形式で表示するための記述ここから <div class="item-box"> <% @body_record.each do |latest_bodyrecord| %> <div class="latest-box"> <table class="latest-body-data"> <tbody> <tr> <th class="latest-data">日付</th> <td class="latest-value"><%= latest_bodyrecord.date %></td> </tr> <tr> <th class="latest-data">体重</th> <td class="latest-value"><%= latest_bodyrecord.body_weight %>kg</td> </tr> <tr> <th class="latest-data">体脂肪率</th> <td class="latest-value"><%= latest_bodyrecord.fat %>%</td> </tr> <tr> <th class="latest-data">体調</th> <td class="latest-value"><%= latest_bodyrecord.todays_condition %></td> </tr> </tbody> </table> </div> #編集・削除ボタンここから <div class="edit-delete-box"> <li class="edit-box"> <%= link_to "編集する", edit_body_record_path(latest_bodyrecord.id), method: :get, class: "btn btn-info" %> </li> <li class="delete-box"> <%= link_to "削除する", "/body_records/#{latest_bodyrecord.id}", method: :delete, class: "btn btn-danger" %> </li> </div> #編集・削除ボタンここまで <% end %> </div> #テーブル形式で表示するための記述ここまで #データが入っていない際の条件分岐 <% else %> <div class="empty"> <p>記録されたデータはありません</p> </div> <% end %> コントローラー側の処理は以下の通りです。 app>controllers>body_records_controller.rb #〜省略 def index @body_records = BodyRecord.all @body_record = BodyRecord.order(date: :desc).limit(1) end def search @body_records = BodyRecord.all @body_record = BodyRecord.new(body_params) if @body_record.date.present? @body_record = BodyRecord.where('date = ?', "#{@body_record.date}}") else @body_record = BodyRecord.none end render :index end #省略〜 ルーティングは以下の通りです。 config>route.rb resources :body_records do collection do post :search end 処理の流れ まず、コントローラー側のindexアクション内の@body_record = BodyRecord.order(date: :desc).limit(1)の記述により、index.html.erbにアクセスした際は、最新の日付のデータが表示されます。 その後、日付を選択して確認するボタンを押すと、searchアクションが動き、データベースの中から選択された日付のデータを取得して、render :indexの記述によりindex.html.erbを再表示する仕組みになっています。 さらに、もし選択された日付にデータが保存されていなかった場合、 <% unless @body_record.empty? %> 省略 <% else %> <div class="empty"> <p>記録されたデータはありません</p> </div> <% end %> の記述により、「記録されたデータはありません」という表示が出るように条件分岐をしています。 終わりに 今回ご紹介した内容は、Railsで実装しましたが、Ajaxで非同期通信を用いて実装するほうが望ましいのではないかな?と考えています。これからアプリケーションをアップデートする中で、より良い方法や手法をどんどん試していこうと考えます。 間違っていたり、おかしなところがありましたら修正いたしますので、遠慮なくコメントいただければと思います。 最後までお読みいただき、ありがとうございました。 参考記事 【Rails入門】form_tagの使い方まとめ | 侍エンジニアブログhttps://www.sejuku.net/blog/29083 Railsで検索機能を実装する方法を現役エンジニアが解説【初心者向け】https://techacademy.jp/magazine/22330 【Rails】パラメーター値の取得(paramsメソッドの使い方https://qiita.com/yuki_0920/items/9f450a51c74407645a3b
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby(Rails)でGoogle Calendar API を本番環境(Heroku)で叩く

概要 Google Calendar APIは認証まわりが非常に難しく魔境と言われています。 プログラミング初心者や公式ドキュメントを読むことが苦手な人であれば大半は挫折することでしょう。 実際のところ、私もトライしてみてかなり苦戦しました。 おそらく、次使う時は極力GAS(Google Apps Script)で実装すると思います。(認証&連携が楽なので) しかし、意地でもRubyを使ってGoogle Calendarを叩きたいという人は少なからずいると思いますし、今後も同じところで躓くひとが多いと思うので備忘録として書き残しておきたいと思います。 前提 Ruby(2.2以上) Railsを使用することを想定(オブジェクト指向で設計) 本番環境:Heroku(無料でタスクスケジューラが使えるため) 公式ドキュメント まずは公式ドキュメントをコピペしてじっくり眺めつつ、わからないところは仕様を調べましょう。  quickstart.rb require "google/apis/calendar_v3" require "googleauth" require "googleauth/stores/file_token_store" require "date" require "fileutils" OOB_URI = "urn:ietf:wg:oauth:2.0:oob".freeze APPLICATION_NAME = "Google Calendar API Ruby Quickstart".freeze CREDENTIALS_PATH = "credentials.json".freeze # The file token.yaml stores the user's access and refresh tokens, and is # created automatically when the authorization flow completes for the first # time. TOKEN_PATH = "token.yaml".freeze SCOPE = Google::Apis::CalendarV3::AUTH_CALENDAR_READONLY ## # Ensure valid credentials, either by restoring from the saved credentials # files or intitiating an OAuth2 authorization. If authorization is required, # the user's default browser will be launched to approve the request. # # @return [Google::Auth::UserRefreshCredentials] OAuth2 credentials def authorize client_id = Google::Auth::ClientId.from_file CREDENTIALS_PATH token_store = Google::Auth::Stores::FileTokenStore.new file: TOKEN_PATH authorizer = Google::Auth::UserAuthorizer.new client_id, SCOPE, token_store user_id = "default" credentials = authorizer.get_credentials user_id if credentials.nil? url = authorizer.get_authorization_url base_url: OOB_URI puts "Open the following URL in the browser and enter the " \ "resulting code after authorization:\n" + url code = gets credentials = authorizer.get_and_store_credentials_from_code( user_id: user_id, code: code, base_url: OOB_URI ) end credentials end # Initialize the API service = Google::Apis::CalendarV3::CalendarService.new service.client_options.application_name = APPLICATION_NAME service.authorization = authorize # Fetch the next 10 events for the user calendar_id = "primary" response = service.list_events(calendar_id, max_results: 10, single_events: true, order_by: "startTime", time_min: DateTime.now.rfc3339) puts "Upcoming events:" puts "No upcoming events found" if response.items.empty? response.items.each do |event| start = event.start.date || event.start.date_time puts "- #{event.summary} (#{start})" end 無駄を省いてモデルに切り出す calendar.rb require "google/apis/calendar_v3" require "googleauth" require "googleauth/stores/file_token_store" require "date" require "fileutils" class Calendar # The file token.yaml stores the user's access and refresh tokens, and is # created automatically when the authorization flow completes for the first # time. ## # Ensure valid credentials, either by restoring from the saved credentials # files or intitiating an OAuth2 authorization. If authorization is required, # the user's default browser will be launched to approve the request. # # @return [Google::Auth::UserRefreshCredentials] OAuth2 credentials def authorize # 環境変数の定義 uri = ENV["OOB_URI"] user_id = ENV["MAIL"] secret_hash = { "web" => { "client_id" => ENV["CLIENT_ID"], "project_id" => ENV["PROJECT_ID"], "auth_uri" => ENV["AUTH_URI"], "token_uri" => ENV["TOKEN_URI"], "auth_provider_x509_cert_url" => ENV["PROVIDER_URI"], "client_secret" => ENV["CLIENT_SECRET"], "redirect_uris" => [ENV["REDIRECT_URIS"]], "javascript_origins" => [ENV["JAVASCRIPT_ORIGINS"]] } } # herokuの環境的に環境変数から読み込んだほうが良い client_id = Google::Auth::ClientId.from_hash secret_hash token_store = Google::Auth::Stores::FileTokenStore.new file: "token.yaml" authorizer = Google::Auth::UserAuthorizer.new client_id, Google::Apis::CalendarV3::AUTH_CALENDAR_READONLY, token_store credentials = authorizer.get_credentials user_id if !credentials url = authorizer.get_authorization_url base_url: uri puts "Open the following URL in the browser and enter the " \ "resulting code after authorization:\n" + url code = ENV["CODE"] credentials = authorizer.get_and_store_credentials_from_code( user_id: user_id, code: code, base_url: uri ) end credentials end # Initialize the API def initialize @service = Google::Apis::CalendarV3::CalendarService.new @service.client_options.application_name = ENV["APPLICATION_NAME"] @service.authorization = authorize end def fetch_events calendar_id = ENV["CALENDAR_ID"] now = DateTime.now + 1 response = @service.list_events(calendar_id, max_results: 5, single_events: true, order_by: "startTime", time_min: DateTime.new(now.year,now.month,now.day,0,0,0), time_max: DateTime.new(now.year,now.month,now.day,23,59,59) ) end end フレームワークを使わずにRubyだけで書くくらいの小規模なコードであれば個人的にGASを使うことを推奨しているので、今回は大規模なシステムにGoogle Calendarを導入することを想定してRailsを使っていると想定しています。 余談ですが、GASでGoogle Calendarから引っ張った情報をRailsに送るという手法もあります。(こっちのほうが楽な人は楽かも) ではひとつずつ見ていきましょう。 initializeメソッド(準備) calendar.rb # Initialize the API def initialize @service = Google::Apis::CalendarV3::CalendarService.new @service.client_options.application_name = ENV["APPLICATION_NAME"] @service.authorization = authorize end calendarクラスでインスタンスが生成されるとまず、initializeメソッドが呼び出されます。 3行目でauthorizeメソッドが呼び出され認証がはじまります。 変数は全て環境変数(ENV)に格納し、githubにプッシュして漏れるリスクをなくしています。 dotenv-railsというgemをインストールすれば.envファイルで一括管理できるのでおすすめです。 間違えて大事なキーをgithubにプッシュしないように気をつけましょう。 dotenv-railsの参考記事 続いて鬼門である認証まわりを見ていきましょう。 authorizeメソッド(認証)  calendar.rb def authorize # 環境変数の定義 uri = ENV["OOB_URI"] user_id = ENV["MAIL"] secret_hash = { "web" => { "client_id" => ENV["CLIENT_ID"], "project_id" => ENV["PROJECT_ID"], "auth_uri" => ENV["AUTH_URI"], "token_uri" => ENV["TOKEN_URI"], "auth_provider_x509_cert_url" => ENV["PROVIDER_URI"], "client_secret" => ENV["CLIENT_SECRET"], "redirect_uris" => [ENV["REDIRECT_URIS"]], "javascript_origins" => [ENV["JAVASCRIPT_ORIGINS"]] } } # herokuの環境的に環境変数から読み込んだほうが良い client_id = Google::Auth::ClientId.from_hash secret_hash token_store = Google::Auth::Stores::FileTokenStore.new file: "token.yaml" authorizer = Google::Auth::UserAuthorizer.new client_id, Google::Apis::CalendarV3::AUTH_CALENDAR_READONLY, token_store credentials = authorizer.get_credentials user_id if !credentials url = authorizer.get_authorization_url base_url: uri puts "Open the following URL in the browser and enter the " \ "resulting code after authorization:\n" + url code = ENV["CODE"] credentials = authorizer.get_and_store_credentials_from_code( user_id: user_id, code: code, base_url: uri ) end credentials end 公式ドキュメントと違うところはsecret_hashという変数に全て格納し、Google::Auth::ClientId.from_hash secret_hash でclient_idを入手している点です。 公式ではGoogleDeveloperからダウンロードしたjsonファイルを読み込む仕様でしたがherokuでjsonファイルを読み込むのは少々面倒なので環境変数で読み込むことは出来ないか検討し、公式のソースコードを読み漁り、hashから読み込む関数があったので書き換えました。 secret_hashの中身はすべてダウンロードしたjsonファイルの中に書いてある変数です。 uriはGoogleDeveloperにリダイレクトURLを登録したものにします。 jsonファイルから読み込む仕様で問題ない場合はこのような書き換えは必要ないです。 実際にこの関数(authorize)を実施しようとするとcredentialsがnilなのでターミナルに認証のURLが表示され、そこのURLにcodeが格納されているためコピペして環境変数(CODE)に格納します。 jsonファイルのダウンロードやredirect_uriについて詳しく知りたい方は↓におすすめの記事を載せておきます。 おすすめ記事 fetch_eventsメソッド(イベント情報の取得) calendar.rb def fetch_events calendar_id = ENV["CALENDAR_ID"] now = DateTime.now + 1 response = @service.list_events(calendar_id, max_results: 5, single_events: true, order_by: "startTime", time_min: DateTime.new(now.year,now.month,now.day,0,0,0), time_max: DateTime.new(now.year,now.month,now.day,23,59,59) ) end 認証がうまく行けばあとはイベント情報を取得するだけです。 こちらもほとんど公式ドキュメントのまんまですが公式の説明が適当すぎて分かりにくかったので軽く解説します。 calendar_idはGoogle Calendarの設定画面から取得します。 時刻は基本USタイムゾーンで返却されるので返却された時刻をJST(日本時間)に変換する必要があります。 DateTimeはrailsのゾーン設定に依存するので極力TimeWithZoneを使うのをおすすめします。 時間の扱い方にあまり慣れていない人は伊藤さんの素晴らしい記事があるので一読することをおすすめします。 RubyとRailsにおけるTime, Date, DateTime, TimeWithZoneの違い max_result : 取得したい最大のイベント数 order_by : 取得するイベントの並び順 time_min : 取得したいイベントの開始時刻の最小値(最も近い時間帯) time_max : 取得したいイベントの終了時刻の最大値(最も遠い時間帯) single_events : これはよくわかんないけどtrueにしたほうが良い(みんなそうしてる) データが取得できたら欲しいデータだけ変数に格納し、データを整形してあげましょう。 私自身ここから先のコードは別のモデルに切り出しました。 responseの値をデバック(ターミナルに吐き出して)ひとつひとつどんな情報が格納されているか観察するのをおすすめします。(response.itemsでイベントの配列が取得) 多くの人が使うであろう値をここに列挙しておきます 題名、タイトル(summary) 開始時刻(start) 終了時刻(end) 場所(location) 場所が設定されてない時や時刻が終日のときはしっかり例外処理をすることをおすすめします。 データの取得、整形 message.rb class Message def organize_from_calendar club_calendar = Calendar.new response = club_calendar.fetch_events if response.items.empty? result = '予定無し' else event =response.items.first start_time = event.start.date_time.in_time_zone('Tokyo').strftime("%H:%M") end_time = event.end.date_time.in_time_zone('Tokyo').strftime("%H:%M") location = event.location title = event.summary #要件は満たせるけど可読性が微妙 #locationがnilではない場合は○○での「で」を追加 location += "で" if location result = "明日は#{start_time}から#{end_time}まで#{location}#{title}があります。\n欠席or遅刻者は背番号+(スペース)遅刻or欠席+(スペース)理由の形式でご回答ください。\n(例)21番 欠席 授業があるため" end end end せっかくなので私が実際に書いたコードを載せておきます。 例外処理はbegin rescueを使って書いても問題ないです。 最後に 公式ドキュメントにはquickstart.rbとか書いてあるのに全然すぐにスタート出来ない&公式ドキュメントは不親切で笑えてきます。 こういうときこそ、公式のドキュメントやgithubのソースコード、英語記事を読む力が問われてきます。 私自身かなり良い訓練になりました。 この記事が少しでも皆さんのお役に立てれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

解決!Datetime型と文字列の比較でハマった話 & 開発環境と本番環境のdbは揃えておいた方がいいねって話  

1. 環境 Ruby : 2.6.6 Rails : 6.0.3.6 db : sqLite 3 (開発環境) | postgreSQL(本番環境) 2. 背景 コロナ禍で、昼食時に会社の食堂に入る人数をコントロールする簡単な席予約アプリを作っている時にハマった話です。 views で、予約したい日付(date)と、時間帯(slot)を選ばせて、controllersのcreateアクションに送る、その際の条件として下記の4つの条件を設定。 1) 過去の日付を予約させない 2) 土日を予約させない 3) 同じ人が同じ日に予約をさせない(昼食は一日一回であろうと想定) 4) 同じ日の同じスロットに入る人数の調整 (現在はテストとして1人以上予約が入ることを阻止) 1),2)は問題なし。しかし3),4)が開発環境ではうまく動作するのに、Heroku上では動作しない。つまり同じ人が同じ日に予約できてしまい、同じ日の同じスロットに入る人数の調整ができない (テストとして1人以上予約が入ってしまう)という現象が発生。 素晴らしいメンターさんの指導で解決に至ったという話です。 3. 結論 (1) timeカラムの設定が datetime 型なので、格納されているレコードは当然datetime型。 一方viewsから送信された値は、"2020-12-11" などという文字列となる。 datetime型と文字列では比較ができない。 (2) よって viewsから送信された値"2020-12-11"を、Timeクラスのzoneメソッド(time zone をつける)とparseメソッド(文字列から日付への変換)を使用してdatetime型に変換する。 @booking_date = Date.parse(params[:booking][:date])  ↓ @booking_date = Time.zone.parse(params[:booking][:date]) (3) それでは、なぜ development環境下では動作していたものが production環境下では動作しなかったのか? どうやらこれは、development および test 環境下では sqlite3 を使用、production環境下ではPostgresQL を使用していたことに起因するようだ。development 環境下で動作したのは、sqlite3が文字列と datetime の比較をやってくれていたからで、PostgresQLはそれら型が違うものの比較はしてくれないというのが原因だった。 ・・・ということで解決! 学びとしては、development, test, production の各環境で使用するdbは同じものを使用するべきであるということ。あとはデータの型に注意ということ。 app/views/bookings/new.html.erb <div class="wrapper"> <h2>Book your table here.</h2> <%= form_with(model: @booking, local: true) do |f| %> <div class="form-group"> <%= f.label :date %> <%= f.date_field :date, class: 'form-control' %> #これで日付を飛ばすと文字列データに・・・ </div> <div class="form-group"> <%= f.label :slot %><br> <%= f.select :slot, [["11:30~11:45", "11:30~11:45"],["11:45~12:00", "11:45~12:00"], ["12:00~12:15", "12:00~12:15"],["12:15~12:30", "12:15~12:30"], ["12:30~12:45", "12:30~12:45"],["12:45~13:00", "12:45~13:00"], ["13:00~13:15", "13:00~13:15"],["13:15~13:30", "13:15~13:30"]], include_blank: "select slot" %> </div> <div class="form-group"> <%= f.submit "Book my table",class: "button-create" %> </div> <% end %> app/controllers/bookings_controller.rb def create @user = current_user @booking_date = Date.parse(params[:booking][:date])   #どうも↑がおかしい!   #のちに@booking_date = Time.zone.parse(params[:booking][:date])に変更して解決! @booking_slot = params[:booking][:slot] # 予約ができる日は本日以降の未来とする if @booking_date < Date.today flash[:danger]= "Head for the future!" redirect_to new_booking_path # 日曜(0)か土曜(6)は予約をさせない。 Date.strptime(@booking_date).wday == 6 elsif @booking_date.wday == 0 || Date.strptime(@booking_date).wday == 6 flash[:danger]= "Dont' be a slave of your job!" redirect_to new_booking_path else #同じ人が同じ日に予約を入れる事を防ぐ…昼食は一日一回であろうと想定 if Booking.where(user_id: @user.id).where(date: @booking_date).any? flash[:danger]= "Double booking in the the day! Please check." redirect_to root_path #同じ日の同じスロットに入る人数の調整…現在はテストとして1人以上予約が入ることを阻止 elsif Booking.where(date:@booking_date).where(slot:@booking_slot).count >= 1 flash[:danger]= "That slot is fully occupied! Please try other slot." redirect_to new_booking_path else app/db/schema.rb ActiveRecord::Schema.define(version: 2020_10_30_034620) do create_table "bookings", force: :cascade do |t| t.datetime "date" #dateカラムにdatetime型を設定 t.string "slot" t.integer "user_id", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.index ["user_id"], name: "index_bookings_on_user_id" end end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby】chromedriverのバージョンエラー(seleniumでスクレイピング)

また同じことをやる日が来る予感しかないので、備忘録的にメモ ※ 環境はmacで、catalina(バージョン10.15.7)です 経緯 いつものようにスクレイピングをしようとすると、chromedriverのバージョンが違うというエラー session not created: This version of ChromeDriver only supports Chrome version 88 (Selenium::WebDriver::Error::SessionNotCreatedError) Current browser version is 90.0.4430.93 with binary path /Applications/Google Chrome.app/Contents/MacOS/Google Chrome 上記のエラーだとバージョンが88しかサポートしてないのに、現在は90のバージョンを使っているのでできませんと怒られています なので90のバージョンを拾ってくる必要がある 欲しいバージョンをダウンロード ↑このサイトからzipファイルをダウンロードしてきます 90.0.4430.24みたいに細かいバージョンが書いてますが、たぶん先頭のバージョンが合ってれば問題ないはず 解凍したファイルをそのまま使おうとすると以下のようなwarningが出て使えない可能性があるので、Controlキーを押しながらアプリケーションアイコンをクリックして、ショートカットメニューから「開く」を選択すればOK warning解消の詳細はこちら アプリを開くとターミナルが立ち上がると思いますが、とりあえずそのまま閉じてOKです chromedriverの保存場所確認 $ which chromedriver # => /usr/local/bin/chromedriver 特に変な設定してなければ上記のように/usr/local/bin/chromedriverにあるはず ファイルを移動 ダウンロード直下にファイルがあれば下記のコマンドで移動しておきましょう $ mv ~/Downloads/chromedriver /usr/local/bin/ これで通常通り動くはず 参考 ほぼ同じ内容ですが下記の記事を参考にしました https://qiita.com/iHacat/items/9c5c186f0d146bc98784
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Paypal,Stripe決済後のサンキューメールを全自動かつ無料で送れるアプリを作った話。

初めて投稿させていただきます、Takaoです。 素人なので間違っている点もあると思いますが、温かい目で見ていただけると嬉しいです。 今回は僕が作成したPayPal,Stripe決済後に自動でサンキューメールを送信するアプリケーションについて書きたいと思います。 以下のリンクからアクセスできます。 PalPalMailer 僕がPaypalやStripeで商品を販売しようとした際に、決済後のサンキューメールを送るアプリをいろいろ探したのですがなかなかしっくりくるのが見つかりませんでした。(というか値段が高い涙)。 僕は商品ごとに送信するテンプレートが選択でき、ステップメール機能もあるアプリが欲しかったのですが、なかなか見つからず(MyASPというサービスを使えばできるようだが月額3000円以上と値段が少し高い)仕方なく自分でこのPalPalMailerを作成しました。 PalPalMailerは無料で決済後のメールを送ることができ、さらに商品ごとに違うテンプレートを送信したり、違うシナリオのステップメールを送ることもできます。 送信したメールが届いたかや開かれたかなどメールのアクションを追跡する機能も付いています。 自分でほしいと思った機能はほとんどつけられたと思います。 アプリの紹介はこれくらいにしてこのアプリに使用した技術について書きたいと思います。 使用した技術 PalPalMailerはとにかくお金をかけずに作成することにこだわりました。 フレームワーク:Ruby on Rails データベース:mongodb atlas プラットフォーム:Heroku メール送信システム:Sendgrid 決済システム:Stripe フレームワーク まずフレームワークはRuby on Railsを選択しました。最初はRails APIでバックエンド、フロントをReactで構成しようと考えていたのですが、認証周りのセキュリティでどうするのが安全なのかわからず結局Rails単体で構成しました(jwtを使った認証があるそうなのですがlocal storageに保存すべきか否かや、Cookieを使用すべきかで意見が分かれる。)。どうするのが正解なのかわかる方がいたら教えていただけると嬉しいです。 認証はdeviseを使用し、権限管理にはcancancanを使用しました。 実装にあたってこちらの記事を参考にさせていただきました。 [Rails] deviseの使い方(rails6版) [Rails5]cancancanってなんぞ? データベース データベースはMongoDB Atlasを選択しました。理由は単純でHerokuのデータベースのpostgreSQLやMySQLは20000行までしか無料でないので無料で512MBのM0 Sandboxが使えるMongodb Atlasを選択しました。HerokuでMongodb Atlasを利用するうえでこちらのサイトが参考になりました。 HerokuでMongoDB Atlasを使う方法 RailsでMongoDBを操作するためにMongoidというgemを使ったのですが若干RailsのActiveRecordと挙動が違って少し苦労しました。いろいろ詰まった時はこちらのサイトを参考にさせていただきました。 Mongoidの挙動の違い Mongoid プラットフォーム 言わずと知れたPaaSのHerokuを利用しました。利用方法についてはほかの方が詳しく書かれているので他の方の記事を参考にしてください。無料で扱うためにクレジットカードを登録して1000時間のdyno時間を使用しています。dynoのスリープを防ぐためにUptimeRobotを使用しています。 UptimeRobotについては以下の記事がとても参考になりました。 UptimeRobot メール送信システム メール送信システムにはSendgridを選択しました。最初はGmailを使用する予定だったのですが、SMTPやAPIでは1日当たり100通までしか送信できないので無料で月間で12000通送信できるSendegridを選択しました。実装に当たってはこちらのサイトが参考になりました。 Rails Sendgrid API これは利用していて気付いたのですがSendgridはアメリカに本社があり、日本語で書かれたサイトは日本の代行業者(構造計画研究所)が運営しているそうで、日本の代行業者でアカウントを作成するのとアメリカの本社のサイトでアカウントを作成するのとでは送信数や利用できるサービスなどに大きく差があります。料金プランなどを見ていて結構混乱したので利用しようとしている方は気を付けてください。 決済システム PalPalMailerを作成するうえで最も苦戦したのは決済システムの実装でした。色々調べましたがStripeが公式ドキュメントも充実しており、導入しやすそうだったので使用することにしました。しかし公式のドキュメントはSinatraで書かれているため公式のドキュメントはあまり参考にならず、こちらの記事を参考にしました。 Rails Stripe実装 StripeはRailsのドキュメントこそ少ないですが仕様や利用法などはきれいにまとめられていて調べて解決することがほとんどだったので今後も使用したいと思います。 最後に PalPalMailerについてはサイトのフォームからお問い合わせお願いいたします。 あと間違っている点があったら教えていただけると嬉しいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsでフォームを作成していたときに知った2点

はじめに プログラミング初学者のため、自分の理解できている範囲内で言語化しています。 何か間違っている情報や改善点などありましたら、コメントいただけますと幸いです。?‍♂️ 今回はRailsでフォームを作るときにハマったことを紹介しています。 1 エラーメッセージ 表示によるレイアウトの形崩れ バリデーションエラーの文言を表示したときに、元のレイアウトが崩れてしまう問題が発生、、、 原因: field_with_errorsクラスを持つdivタグの出現 エラーメッセージを表示するときは、検証するとわかるが、field_with_errorsクラスのdivが現れる。 これによって形崩れしていた? 解決策 他にもあるが、ここではCSSに追記をするだけで良い方法を紹介 .field_with_errors { display: contents; } これを追記するだけ。 2 text-areaの縮小拡大について text-areaはデフォの状態では、横と縦にドラッグすることで縮小や拡大が可能だが、 今回の実装の際には左右の縮小はいらなかったので、その抑制方法の紹介 方法 textarea{ resize: vertical; } これで横の縮小を抑制できる 横方向 → vertical 縦方向 → horizontal 全方向 → both デフォでなっている 縮小無 → none 参考文献
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

1 〜 9 の分数を足して 1 になる数

1 〜 9 で作れる分数の数を足して 1 に最も近くなる数を求める 以下のような投稿が話題だった。 中身を要約すると (1/3) + (1/4) + (1/6) + (1/7) + (1/9) ≒ 1.0 という驚くべき?等式が成り立っている点である。 もっと良さそうな値がないか全探索で解いてみた k 項目を利用するかしないかを変数 $ a_k $ で表すと、それらの合計 \sum_{k=1}^9 \frac{1}{k} a_k が 1 に近い順に昇順で出せばよい。 今回、足し算は交換可能なので順序は考慮しなくてよい。 したがって、計算量は $ 2^9 = 512 $ 通りになるため、全探索でも問題なさそう。 ↓計算に使った ruby のスクリプト def solve(xs, u, d, ans) if (u.to_f/d.to_f).abs < 0.02 return yield(u,d,ans) end return if xs.empty? x = xs.pop solve(xs, u, d, ans) do |u,d,ans| yield(u,d,ans) end solve(xs, u*x-d, d*x, ans.push(x)) do |u,d,ans| yield(u,d,ans) end ans.pop xs.push x end def gcd(u, d) return u if d == 0 gcd(d, u % d) end items = [] solve((2..9).to_a, 1, 1, []) do |u, d, ans| g = gcd(u, d) items.push({ans: ans.dup.map{|x| "1/#{x}"}.join("+"), val: (u.to_f/d.to_f).abs, fraction: "#{u/g+d/g}/#{d/g}"}) end items.sort_by {|x| x[:val]}.each do |item| puts "#{item[:ans]}=#{item[:fraction]} # #{item[:val]}" end ↓結果 1/6+1/3+1/2=1/1 # 0.0 1/9+1/7+1/4+1/2=251/252 # 0.003968253968253968 1/9+1/7+1/6+1/4+1/3=251/252 # 0.003968253968253968 1/9+1/8+1/7+1/6+1/5+1/4=2531/2520 # 0.004365079365079365 1/8+1/6+1/5+1/2=121/120 # 0.008333333333333333 1/7+1/6+1/5+1/2=104/105 # 0.009523809523809525 1/9+1/8+1/4+1/2=73/72 # 0.013888888888888888 1/9+1/8+1/6+1/4+1/3=73/72 # 0.013888888888888888 1/8+1/7+1/6+1/4+1/3=55/56 # 0.017857142857142856 1/8+1/7+1/4+1/2=55/56 # 0.017857142857142856 1/9+1/8+1/5+1/4+1/3=353/360 # 0.019444444444444445 これを見ると 1/6+1/3+1/2=1 として 1 に完全に等しくなる場合を求まる場合を除いて 投稿の内容の結果は最も 1 に近そうである。 1/9+1/7+1/6+1/4+1/3=251/252 以下も同じ結果を出すが 1/9+1/7+1/4+1/2=251/252 これは 1/3+1/4+1/6 と 1/2+1/4 の合計がどちらも 3/4 で等しいためである。 ちなみにその次に近い数は 1/9+1/8+1/7+1/6+1/5+1/4=2531/2520 になりそう。分母と分子が 1 違いの場合は 1/8+1/6+1/5+1/2=121/120 でした。 結果 完全に合計が 1 になる場合を除いて 以下の分数列の合計が最も式が長く 1 に近い式である。 (1/3) + (1/4) + (1/6) + (1/7) + (1/9) ≒ 1.0 したがって、件の投稿はもっとも完全に 1 になる場合を除いて最も効率のよい数でした。 おわり
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む