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

Rails6 Ajaxを使えるようにするために最初にしておくこと

ポートフォリを作成中にAjaxで路頭に迷ったので、未来の自分のためにこの記事を残しておこうと思います。

jQuery関係

jQueryのライブラリをインストール

$ yarn add jquery@3.4.1

Webpackの環境編集

config/webpack/environment.js
const { environment } = require('@rails/webpacker')

#下記を追加
const webpack = require('webpack')
environment.plugins.prepend('Provide',
  new webpack.ProvidePlugin({
    $: 'jquery/src/jquery',
    jQuery: 'jquery/src/jquery'
  })
)
#ここまで
module.exports = environment

application.jsに追加

app/javascript/packs/application.js
require("jquery")

ブラウザ関係

ブラウザ側でjavascriptが無効になっていた場合でもAjaxが起動するために

config/application.rb
module "アプリ名"
  class Application < Rails::Application
    .
    .
    .
    # これを追加!
    config.action_view.embed_authenticity_token_in_remote_forms = true
  end

あとは書きまくるだけ!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby】演算にnilが含まれた際のエラーを防ぐ方法

計算式にnilが含まれているとエラーが出る

例えば、こんな単純な計算結果(下記 @x)を出力しようとした場合、nilが含まれていると、何かしらのエラーが出るはずです。

a = 1
b = nil
@x = a + b

対策

.to_iメソッドを使う。
to_iとは、文字列を(整数と認識できれば、)整数に変換するメソッドです。
整数と認識されなかったものは、0を返します。

nilの場合も、整数を認識されないので、0を返し、エラーを出さなくなります。

上記、@xを出力しようとするなら、

@x.to_i

と書けばOKです!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】よっ!Active Strage!有能なGem!〜2つのテーブルから画像を保存できる〜

ずっと謎だったこと

Active Strageで画像を保存する時に二つのテーブルから保存できるのか?ということがずっと疑問でした。
現在オリジナルアプリを開発中なのですがレビューサイトのため、
①ユーザーのプロフ写真
②レビュー対象の写真
この二つを保存したかったのですが複数のテーブルから保存する方法がいまいちよくわからなかったため、ユーザーのほうはtext型にて保存、Active Strageを使用するのはレビュー対象のみとしていました。
本日、マイグレーションをrollbackしなければいけないミスがあり、ついでだからやってみようと思い立ち、以下の作業をしました。

・ユーザーテーブルに保存していた画像保存予定だったカラムを消去
・ユーザー登録時&レビュー対象登録時にそれぞれ写真が保存できるか確認

まずはマイグレーションを編集します。

% rails db:rollback  

このコマンド、最初は実行するのがとてもとても怖かったです(初心者あるある
その後、状況がどうなっているかをしっかり確認するためステータスをチェック。

% rails db:migrate:status  

database: party_freak_development

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20210107124355  Devise create users                 ⬅️変更目標はここ
   up     20210108071047  Create parties 
   up     20210108094951  Create active storage tablesactive storage
   up     20210108122152  Rename iintroduction column to parties  ⬅️リネームした履歴
   up     20210108123328  Rename date id column to parties      ⬅️リネームした履歴
  down    20210108130921  Change column to allow null      ⬅️null制約変更した履歴

下の3つは昨日変更したテーブルのカラムの名前とnull制約に関するマイグレーションファイルです。
よくよく考えたらリネームしてマイグレーションファイルを増やすならrollbackしてマイグレーションファイルを直に修正したほうが余計なファイルが増えなくていいんじゃないか?
と気付き、rollbackを繰り返し行って下の3つのマイグレーションファイルをコードエディタ上から右クリック→削除しました。スッキリ。

繰り返す時はこのコマンドが便利です。

 % rails db:rollback STEP=5 
                ⬆️=の後にdownを0とし(次のupから1と数える)その回数だけrollback
                 今回の自分のケースだと5でした

よく見たら一瞬焦ったこと

== 20210108094951 CreateActiveStorageTables: reverting ========================
-- drop_table(:active_storage_attachments, {})
   -> 0.0470s
-- drop_table(:active_storage_blobs, {})
   -> 0.0363s
== 20210108094951 CreateActiveStorageTables: reverted (0.0847s) ===============

Active Strageがdropになってるううううううううううううう!
またやらかしたああああああああああああああああ!
消したものはしょうがないどうせまた戻るだろ前しか見えねえ(確認犯
db:migrateで、きっとまた会えるよね?そう信じて次に進む。

ユーザーモデルにActive Strageを使えるようにする記述を行います。

app/models/user.rb
class User < ApplicationRecord

  has_one_attached :image  ⬅️これを記述

〜以下略〜

end

①ユーザーモデル
②レビュー投稿モデル
合計2つのモデルにhas_one_attached :imageを記述しました。

そしてマイグレーションファイルから画像保存用の記述を削除して、db:migrateをする。

結果

5d39289236b741ec2dcc8f2c4ed856eb.png

record_typeというカラムにUserPartyという2つの表示がされています。

大成功!(やっぱり会えたねActive Strage

気になったこと

ユーザーは複数枚の画像投稿(もしくは動画)が可能なように考えているのですが、もう1つテーブルを作ってそこにhas_many_attachedを使って保存されるのだろうか?
複数の場合はhas_oneからhas_manyに変わるとのことですがこのカラムを見ているとできそうな雰囲気も感じますが、実際にやってみた結果はまたこちらで記事を書こうと思います。

以上、Active Strageで2つのテーブルから画像を保存する方法でした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyで値が〇の倍数であることを判別する

どうもこんにちは。
Rubyを始めてみたのですが任意の値が倍数かどうか確かめる方法が調べてもなかなか出てこなかったのでメモ。

判別には

AをBで割った余りを出してくれる % を利用します。
指定した倍数であれば0が出力されるはずです。

コード

sample.rb
a = 10
b = 5
if a % b == 0
 puts "#{a}は5の倍数です。"
 else
 puts "#{a}は5の倍数でありません。"
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【個人開発】テストユーザーを募集するサイト作ってみた!

初めに

前、MENTAの作成者さんが動画で「webアプリは公開してからじゃないと使われるかわからない」って言っていたので
ベータ版専門のアプリ宣伝サイトをつくってみました。
そんなことサイトもまだベータ版でデザインは相当雑です。使われるようになってきたらデザインも変更しますし機能も追加します。

URL:
https://testuser-b.herokuapp.com/

テストユーザーはなぜ必要?

webアプリは公開しないと需要があるかわかりません。
「市場調査で調べられる」と思うかもしれないが口先だけ使うと言われただけで、公開したあとその人たちが本当に使うかはわからないんです。

だから、本当に需要があるかどうかは実際に公開してみるしかないのですが
何か月もかけて開発したのに誰にも使われなかった...というのは悲しすぎます。
webアプリは当たる確率の方が低いです(6%程)

だからヒットしなくてもダメージが少ないようベータ版を作ればいいのですが なかなか使ってもらうのは難しいです。webアプリは万全の状態で宣伝するものなので 今あるwebアプリの宣伝所はベータ版をなかなか受け入れてくれません。 そこでこのTest User βが生まれました。

test-user.png

使った技術
rails6
ruby2.7
heroku free(スリープしないよう設定している)

開発時間は数時間
ロゴは自作
開発費用もサーバー代も無料です。

まとめ

webアプリは公開してからじゃないと使われるかわからない
市場調査だけではわからない

何がともあれ使ってみてください。お願いします(^_-)-☆
URL:
https://testuser-b.herokuapp.com/

僕のツイッターもお願いします。
開発中に気づいたことをつぶやいてます。
https://twitter.com/Yuuki49079799

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsでExtend(Concerns)を利用してControllerの処理を共通化する。

背景

記事一覧をPage#homeに出力したかった。
@ posts = Post.allのデータは、Postモデル、コントローラにある。Pageモデル、コントローラには無い。
PostのデータをPageに持ってくるためにはどうすればいいかググっていた。

解決

この情報をPageに持って行きたい。

posts_controller.rb
class PostsController < ApplicationController

    def index
      posts = Post.all
    end
end

Pageに取り込む。

Page.rb
class Page < ApplicationRecord

   + extend Post::Models

end

PageでPostの情報を定義してみる。

pages_controller.rb
def home
  @posts = Post.all
end

出力された。

home.html.erb
<%= @posts.each do |p| %>
 <%= p.thumbnail %>
 <%= p.title %> 
 <%= p.tag %> 
 <%= p.content %> 
<% end %> 

もっとうまく説明している記事あります。↓
https://github.com/mc-chinju/qiita_clone/commit/262a6178d5a2eb77c6f507cf9386cb61825bfbaf
https://medium.com/@yavuz255/rake-aborted-2da1233a4561
https://railsguides.jp/active_model_basics.html

他のやり方↓
controller/concernにファイルを作って行う方法
https://programming-beginner-zeroichi.jp/articles/142

ググる時の関連ワード

extend ActiveSupport::Concern
rails concern

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

yarnがインストールされていないとのこと

rails new でアプリを作ろうとすると

RubyとRailsがインストールでき、アプリを作ってみようかとすると
yarnがインストールされていませんよと言われたので、yarnをインストールして解消。

環境
Mac
Ruby2.6.0
Rails6.1.0

rails new実行

今回はデスクトップに作るので、

$ cd Desktop
$ Rails new アプリ名

実行すると、
しばらく時間がかかったのちに、

Yarn not installed. 

アプリが作られず、エラーになったようで、上記のようにyarnがインストールされていない
と言われる。

yarn インストール

参考→Rails6 開発時につまづきそうな webpacker, yarn 関係のエラーと解決方法

$ brew install yarn
$ yarn -v
$ yarn install

これで無事にインストールされた模様。

再度アプリを作りサーバーで実行

再度、rails new を実行すると今度はうまくいった。
そして、rails server を実行すると、懐かしの画面。1年半ぶり?
image.png

ん、Railsのバージョンが変わってる?
yarnのインストールによってRailsのバージョンが6.1.0から6.1.1になったのでしょうか。

$ rails -v
Rails 6.1.1

やはり変わっている。問題ないでしょう。知らんけど。
さて、Rubyの勉強を少し挟んで、Railsアプリの中身を作ることにします。

参考書籍

Ruby on Rails 6 超入門

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyで黒澤ルビィを作る(Ruby + LINE Messaging API)

黒澤ルビィ、可愛いですよね。

LINE上で喋るルビィちゃんを作りました。Rubyで。

Rubyボット01.png

以下の仕組みで動いています。

  • LINE Messaging APIを用いて、「黒澤ルビィ」としてLINEメッセージの受信、送信を行う。
  • Ruby(Ruby on rails)LINE Messaging APIとやり取りするAPIサーバを作る。
    • メッセージを形態素解析し、その結果をもとにマルコフ連鎖で新しく文章を生成する

LINEアカウントを作る

前準備としてルビィちゃんとして動くアカウント(厳密に言うと、Messaging APIのチャネル)が必要です。それをつくります。

LINE Developersから登録します。
Rubyボット02.png
Create a new channelから新しく作ります。なお、作るときにどういうTypeか聞かれると思います。今回作りたいものはMessaging APIで作ることができるので、該当するものを選択してください。

無事作成できると、アクセストークンを取得できます。
また、アカウントのアイコンや自己紹介欄などを設定できます。1

APIサーバを作る

画面を持つ予定はないので、APIモードでRailsプロジェクトを作ります。
line-bot-apigem2を入れます。先程作成したアカウントのアクセストークンを使ってLINEとやりとりできるようになります。

Controllerを作る

先程作成したアカウントに対してWebhook URLを設定すると、そのアカウントに対して送信したメッセージをトリガーにしてLINEがリクエストを送ってくれます。
具体的なリクエストの中身についてはMessaging APIリファレンスを参照してください。以下の例はテキストメッセージに反応して、「メッセージ」と返すだけの仕組みです。

def client
  @client ||= Line::Bot::Client.new { |config|
    config.channel_secret = ENV["LINE_CHANNEL_SECRET"]
    config.channel_token = ENV["LINE_CHANNEL_TOKEN"]
  }
end

def callback
  body = request.body.read

  signature = request.env['HTTP_X_LINE_SIGNATURE']
  head :bad_request unless client.validate_signature(body, signature)

  events = client.parse_events_from(body)

  events.each do |event|
    next unless event.is_a?(Line::Bot::Event::Message)
    next unless event.type == Line::Bot::Event::MessageType::Text

    client.reply_message(event['replyToken'], message)
  end
  head :ok
end

def message
  # cf. https://developers.line.biz/ja/reference/messaging-api/#text-message
  {
    "type": 'text',
    "text": 'メッセージ'
  }
end

このmessageに相当する部分を、マルコフ連鎖で生成した文章にさせます。

マルコフ連鎖で文章を作る

マルコフ連鎖による文章作成の大枠については解説しているものがたくさんあるので、詳しくは「マルコフ連鎖 文章作成」とかで検索してください。

ざっくり説明すると、あらかじめ形態素解析(言語で意味をもつ最小単位に分解すること)によって言葉のつながりを学習させ、ある単語に対してそれに続く言葉を過去の学習から確率的に選択していくことでそれっぽい文章ができるやつです。

形態素解析する

形態素解析は、Yahoo!が提供している日本語形態素解析を使っています。MeCab(Natto)を使って、オタク用語に特化した辞書を入れるのもアリかなと思います。

LINEメッセージが送られたらそのメッセージをそのまま形態素解析します。結果をパースし、言葉と品詞がセットとなったハッシュの配列にします。3
単語として保存するWordモデルと、つながりを保存するMarkovDicモデルを作り、それぞれ保存します。

Wordモデル

カラム名 説明
pos String 品詞(例:名詞)
surface String 単語(例:庭)

MarkovDicモデル

カラム名 説明
prefix_1 String 単語のつながりをA-B-Cと表現したときのA (例:私)
prefix_2 String 単語のつながりをA-B-Cと表現したときのB (例:は)
suffix String 単語のつながりをA-B-Cと表現したときのC (例:犬)
  # 形態素解析を行い、WordレコードとMarkovDicレコードを作成する。名詞の配列を返す。
  def record(text, user)
    text = remove_url(text)  # URLを取り除いたりしている
    morphological_words = exec(text)

    poses = save_words(morphological_words, user)
    save_markov_dics(morphological_words)

    poses
  end

  # 単語を保存する。
  def save_words(morphological_words)
    words = []

    morphological_words.each do |word|
      w = Word.new(word)
      words << w.attributes.compact!.merge({ created_at: now, updated_at: now })
    end
    Word.insert_all(words) if words.present?
  end

  def save_markov_dics(morphological_words)
    markov_dics = []
    morphological_words.each_cons(3) do |p1, p2, suf|
      next if p1['surface'] == "\n"
      next if p2['surface'] == "\n"

      md = MarkovDic.new(prefix_1: p1['surface'], prefix_2: p2['surface'], suffix: suf['surface'])
      md.suffix = 'END_OF_SENTENCE' if md.suffix == "\n" # 文章の終わりを意味するフラグ
      markov_dics << md.attributes.compact!.merge({ created_at: now, updated_at: now })
    end
    eos = morphological_words.last(2)
    if eos.size == 2
      p1 = eos[0]
      p2 = eos[1]
      md = MarkovDic.new(prefix_1: p1['surface'], prefix_2: p2['surface'], suffix: 'END_OF_SENTENCE')
      markov_dics << md.attributes.compact!.merge({ created_at: now, updated_at: now })
    end
    MarkovDic.insert_all(markov_dics) if markov_dics.present?
  end

文章を作る

LINEに送られたメッセージを保存することができたので、今度は文章を作ります。
始まりの言葉は受け取ったLINEのメッセージからランダムに名詞で始まるMarkovDicを選んでいます。
ざっくり以下のような感じです。

  def create_sentence(text, markov_dic)
    return text if markov_dic.suffix == 'END_OF_SENTENCE'

    next_markov_dic = MarkovDic.where(prefix_1: markov_dic.prefix_2, prefix_2: markov_dic.suffix).sample(1).first
    return text unless next_markov_dic

    text << markov_dic.suffix
    create_sentence(text, next_markov_dic)
  end

  def prefixes
    prefix_1 + prefix_2
  end

Webhook URLを設定する

作成したAPIサーバをデプロイします。(今回はHerokuにデプロイしました。)
最初に作成したアカウントの設定画面でWebhook URLを設定し、メッセージがきたタイミングでLINEがリクエストを送ってくれるようにします。
Rubyボット03.png

LINEグループに追加します。いい感じにレスポンスしてくれます。

Rubyボット05.png

その他

  • グループラインにルビィちゃんを混ぜたところ、「かわいい」「キメラが誕生した」「新しいおもちゃ」と非常に好評でした。
  • クソデカ羅生門を覚えさせた人がいてめちゃくちゃになりました。
  • グループラインにBotアカウントは一つまでしか入れられないようです。黒澤姉妹で話すことはできませんでした。
  • 常に返信がくるとグループラインがやかましくなるので、ランダムで投稿したりしなかったりさせています。(ちなみに直接ルビィちゃんに話しかけると100%返信が来ます。)
  • push_messageを使うとルビィちゃんから話しかけることもできます。
  • 本当はアカウントを紹介したいのですが、記録した言葉の中に含まれる個人情報を考慮すると中々難しいものがあります。
  • 今の所ルビィちゃん要素が0なので、ルビィちゃんっぽくさせたいです。

まとめ

LINE Messaging APIRubyを用いて黒澤ルビィを作りました。

LINE Botは意外と簡単に作ることができるので、ぜひ作ってみてください。


  1. 開発用のアカウントも合わせて作ったりしています。ちなみに画像は友達が描いたやつです。 

  2. line/line-bot-sdk-ruby: LINE Messaging API SDK for Ruby 

  3. 以下を参考にしました。Ruby on Rails で Yahoo のテキスト解析 APIを使う-スケ郎のお話 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby On Rails / HTML】ウェブページ表示時、カーソル(キャレット)を適当な場所へ合わせ表示する方法

備忘録です。

カーソルとキャレットとは

スクリーンショット 2021-01-10 15.31.03.png

キャレット≒カーソル
マウスカーソルなどと区別したい場合にキャレットという言葉が用いられます。

本題

カーソルを適当な場所へフォーカスするには、autofocusを使います。
Rubyでは、次のような書き方をします

autofocus: true

それでは、Deviseの新規登録画面に"Nickname"という項目を設けた場合で見ていきます。
まずは"Nickname"にautofocusを使わない場合はどのように表示されるでしょうか。

①"Nickname"にautofocusなし

new.html.erb
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

  <div class="field">
    <%= f.label :nickname, "Nickname" %><br />
    <%= f.text_field :nickname %>
  </div>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

(後略)

スクリーンショット 2021-01-10 15.39.34.png

上記の場合、新規登録のウェブページを開いてみると、Nicknameではなく、Emailにカーソルがフォーカスされています。

次に、<%= f.text_field :nickname %>にautofocusを付与して、Nicknameにカーソルの焦点を当てた状態で見ていきます。

②"Nickname"にautofocusあり

new.html.erb
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

  <div class="field">
    <%= f.label :nickname, "Nickname" %><br />
    <%= f.text_field :nickname, autofocus: true %>
  </div>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

(後略)

スクリーンショット 2021-01-10 15.47.50.png

このようにautofocusを"Nickname"に付与してあげることで、自動的に一番最初にカーソルが当てられる場所がNicknameとなりました。

参考記事

https://www.sophia-it.com/content/%E3%82%AD%E3%83%A3%E3%83%AC%E3%83%83%E3%83%88
http://www.htmq.com/html5/input_autofocus.shtml

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

バックエンドの設計を任されたので、エンドポイント設計をしっかり学んでみる

はじめに

こんにちは、大学を休学してスタートアップでWebエンジニアとして働いてる、おにかんです。

現在、関わっているプロジェクトのエンドポイント設計を任されたので良い機会だと思い、1から勉強して、まとめてみました。何かご指摘等ありましたらコメントしていただけるとありがたいです。

エンドポイント設計を始めよう!

*そもそもエンドポイントとは?

APIにアクセスするためのURIという認識で問題ないかと思います。

基本的な考え方

URI設計は「シンプルでわかりやすい」ものであるべきです。なぜ「シンプルでわかりやすい」URIが良いのでしょうか?主な理由に「開発者のミスを減らすことができる」ということが挙げられます。例えば、下記の二つのURI、どちらがわかりやすいでしょうか?

api/v1/p/c
api/v1/posts/comments

前者の方は頭文字しかURIに反映されていなく、わかりにくいですよね。一方、後者はある投稿のコメントのリソースなのだな、となんとなく想像がつくかと思います。URIをわかりやすくすることで、開発者のミスを減らすことができ、結果的にサービスの品質が向上します。

「シンプルでわかりやすい」URI設計にするために、以降から具体的なtipsをまとめておきました。

【1】第三者が読んで理解できる

api/v1/p/c

上記のようなエンドポイントでは何を示しているのか予想しにくいですよね。上記のような場合では少なくとも下記のように明確にリソースを示してあげた方が親切です。

api/v1/posts/comments

【2】基本的には全て小文字を使う

どちらかに統一したほうがわかりやすいのでミスが減ります。

【3】他のエンドポイントと整合性をとる

例えば下記のようなエンドポイントは整合性が取れていません。(少し奇妙ですが)

api/v1/posts/:id
api/v1/friends/?id=1

上記は極端な例ですが、下記のようにできるかぎり形式を統一させたほうがわかりやすいかと思います。

api/v1/posts/:id
api/v1/friends/:id

基本的には、この三つが大切です。もっと細かい点で言うと

  • 変更しやすいURL
  • サーバーのアーキテクチャがわかりずらいURL

といったところでしょうか。

HTTPメソッドとエンドポイントの関係

URIとHTTPメソッドはそれぞれ、下記のような役割を持ちます。

URI:リソース(情報)
HTTPメソッド:動作

つまり、URI(情報)をHTTPメソッドで操作することができます。例えば、HTTPメソッドのGETは「取得」を表すので

GET api/v1/posts/100

とすると、PostのIDが100のデータ(リソース)を取得(動作)することができます

大切なことはURIと、HTTPメソッドは分離しているということです。URIで動作(例えば、取得や削除など)を指定することは推奨されませんし、HTTPメソッドがリソースの領域を侵すこともおすすめできません。

以下から主要なHTTPメソッドについて軽く解説していきます。

GET

GETメソッドは「取得」を示します。URIで指定されたリソースを取得してクライアントに返します。下記は例です。

GET api/v1/posts/100

大切なことはGETメソッドで「情報(リソース)」の内容が変化してはいけない、ということです。リソースを作成したい場合は基本的にはPost、更新はPUT、またはPATCH、削除にはDELETEメソッドを使うべきです。

POST

POSTメソッドは「作成」を示します。もっと言うと、「指定したURIに属するリソースを送信する」と言うことです。例を見てみましょう

POST api/v1/posts

GETの時にあった、id指定がなくなっているのが確認できるかと思います。つまり、Postsに属する形でリソースが作られるということです。基本的に、POSTは「新規作成」に使われるべきであり、既存のリソースに対して何かしらの操作をしたい場合は、PUTDELETEを使うと良いと思います。

PUT

PUTメソッドは情報を「更新」される場合に使われます。PUTメソッドはPOSTと違って、更新したいURIを指定します。下記は例です。(実際にはクエリパラメータでデータを指定したりするのですが、今回は割愛)

PUT api/v1/posts/100

POSTメソッドと違って、直接指定しているのが確認できると思います。(POSTは従属するリソースを作成する。)
ちなみに指定したリソースが存在しない場合、リソースの作成が行われます。しかし、リソースの作成には基本的にPOSTメソッドを使うべきです。

DELETE

DELETEメソッドは指定したURIのリソースの「削除」を行います。

DELETE api/v1/posts/100

PATCH

PATCHメソッドは、PUTと同じように「更新」の役割を持ちますが、全ての情報を更新するのではなく、一部の情報を更新します。

HTTPメソッドについては以上です。以降から、URIとHTTPメソッドを組み合わせて、具体的なエンドポイントの設計をしていきたいと思います。

実際にエンドポイントを設計する

基本的なリソースとHTTPメソッドについて見ていきました。ここから実際に二つを組み合わせて見ていきましょう!

今回はmemberというデータモデルに対して、エンドポイントを考えてみます。

個別のデータを取得する

GET /members/:id

membersという集合データをidで特定するというイメージです。基本的にはこの形が一般的です。

全体を取得する

GET /members

membersの全ての情報を取得しています。また検索等は、このエンドポイント内のクエリパラメータで制御するイメージです。

データの新規作成

POST /members

今度はid指定されていません。これはPOSTメソッドであるため、membersに従属するデータを新規作成するというところからきています。

データの更新

PUT /members/:id

更新では、実際に更新するmemberを指定してあげるため、id情報を与えています。

データの削除

DELETE /members/:id

削除の場合も同様にmemberを指定してあげるため、id情報を与えています。

基本的な部分は以上です。あとは実戦でフィードバックをもらいながらやっていくのがいいのかなと思います。

ではでは〜

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Ruby] ArgumentError の本質を見る

ArgumentError

Argumentとは"引数"と言う意味です。
ArgumentErrorは「引数の数が合わないよー」というメッセージになります。
様々なケースが考えられますので、
本質的な基礎の部分を例に考えて行こうと思います。


wikiで「Rubyを対話的に実行 (REPL) するためのシェル」と紹介されている
irbを使っていきます。

下記の実引数と仮引数を用いたコードを、irbで実行

def test(number)
  puts number * number
end

test(5)

Image from Gyazo
testの引数に(5)を設定して仮引数(number)に値を渡すと、
25という結果が出力されました。


では次に、実引数なしでtestを実行してみます。
Image from Gyazo

するとエラーが発生しました。
Image from Gyazo

エラーメッセージを翻訳するとこうです。
Image from Gyazo
コンピューターさんがこう言ってます
「引数が1つあることを期待してたのに、0だったよ」
引数が一つ足りないんですね。


じゃあ今度は実引数の数を2つにして実行してみます。
Image from Gyazo

またエラーです。
Image from Gyazo

今度はこう言っていますね
「引数が1つあることを期待してたのに、2つだったよ」
一個多いよと怒っています。

実引数(5, 6)を2つ送ろうとしても、受け取る側の仮引数(number)の数が1つだと上手くいかないんですね。

まとめ

ArgumentErrorは「引数の数が合わないよー」というエラーです。
メソッドを定義してる箇所の、実引数・仮引数を確認してみましょう!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[フォームオブジェクト]1つのフォームをPOSTでもPATCH(PUT)でも使えるようにする方法

1つのフォームを複数のHTTPメソッドで使い分ける方法です。
フォームオブジェクトで実装すると難しかったのでメモ。

他にもっと良い方法がある場合は遠慮なく教えていただけると嬉しいです:innocent:

こんな方にオススメ

・フォームオブジェクトでフォームを実装している方。
・フォームで情報を送信する際に、新規作成と編集で同じフォームを使いたい方。

やることは2つだけ

(1) フォームを呼び出す側(render)に method: :〇〇を追加。(〇〇はpostやpatchなど)
(2) フォーム側に method: methodを追加。

(1) フォームを呼び出す側

example.html.erb
<%= render "フォームテンプレートのパス", url: 呼び出したいアクションのpath, method: :〇〇 %>

新規作成の場合は〇〇をpostに、編集の場合はpatch(put)にすればOKです。

(2) フォーム側

_example_form.html.erb
<%= form_with model: モデルのインスタンス, url: url, method: method do |f| %>

普通はここでurlやmethodを直接指定すると思うのですが、あえて変数にしています。
そうすることで、新規作成でも編集でも使えるようにしています。

背景

renderでPATCHのパス(url)を定義したはずなのに、実際にはPOSTで送信されていることがありました。原因は下記の記事にあるように、フォームのmodel: に記述したインスタンスが既存であればPATCH, 既存でなければPOSTとなるからだと思われます。フォームオブジェクトを使うとフォーム送信のたびにインスタンスを生成するので、常にPOSTとなり期待した動きをしてくれません。そこで、methodも定義してこの問題を回避しています。

参考にした記事はこちら

https://qiita.com/snskOgata/items/44d32a06045e6a52d11c#2-form_with%E3%81%AE%E5%8B%95%E4%BD%9C%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6

環境

ruby: 2.7.1
rails: 6.0.3.3

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

大貧民(どう書く)

http://nabetani.sakura.ne.jp/hena/ord5dahimi/

トランプゲーム「大貧民」(「大富豪」ともいいます)の場の札と手札が与えられた場合、次に出せるすべての手の組み合わせを出力する問題です。

ジョーカーがワイルドカードになっているので、それをどう処理するかが考えどころになります。ここではジョーカーが手札に含まれている場合の処理を別にしました。

Ruby
class Card
  Rank = %W(3 4 5 6 7 8 9 T J Q K A 2)
  def initialize(str)
    @value = str
    @rank = (str == "Jo") ? 13 : Rank.index(str[1])
  end
  attr_reader :value, :rank

  alias to_s value
end

module Daihinmin
  module_function

  def play(input)
    table, hand = input.split(",")
    return "-" unless hand 
    hand = hand.scan(/../).map { |c| Card.new(c) }
    joker = hand.find {|c| c.value == "Jo"}    
    table = table.scan(/../).map { |c| Card.new(c) }
    t_rank = table.map(&:rank).min
    t_num = table.size

    cs = hand.group_by(&:rank).select { |k, v| k > t_rank }.values
    result = cs.select { |ary| ary.size >= t_num }
               .flat_map { |ary| ary.combination(t_num).to_a }
    if joker && t_num >= 2
      result +=
        cs.select { |ary| ary.size >= t_num - 1 && ary[0] != joker }
          .flat_map {|ary|
            ary.combination(t_num - 1).map { |cards| cards + [joker] }
          }
    end

    result.empty? ? "-" : result.map(&:join).join(",")
  end
end


if __FILE__ == $0
  [
    "DJ,",
    "H7,HK",
    "S3,D4D2",
    "S9,C8H4",
    "S6,S7STCK",
    "H4,SAS8CKH6S4",
    "ST,D6S8JoC7HQHAC2CK",
    "SA,HAD6S8S6D3C4H2C5D4CKHQS7D5",
    "S2,D8C9D6HQS7H4C6DTS5S6C7HAD4SQ",
    "Jo,HAC8DJSJDTH2",
    "S4Jo,CQS6C9DQH9S2D6S3",
    "CTDT,S9C2D9D3JoC6DASJS4",
    "H3D3,DQS2D6H9HAHTD7S6S7Jo",
    "D5Jo,CQDAH8C6C9DQH7S2SJCKH5",
    "C7H7,S7CTH8D5HACQS8JoD6SJS5H4",
    "SAHA,S7SKCTS3H9DJHJH7S5H2DKDQS4",
    "JoC8,H6D7C5S9CQH9STDTCAD9S5DAS2CT",
    "HTST,SJHJDJCJJoS3D2",
    "C7D7,S8D8JoCTDTD4CJ",
    "DJSJ,DTDKDQHQJoC2",
    "C3H3D3,CKH2DTD5H6S4CJS5C6H5S9CA",
    "D8H8S8,CQHJCJJoHQ",
    "H6D6S6,H8S8D8C8JoD2H2",
    "JoD4H4,D3H3S3C3CADASAD2",
    "DJHJSJ,SQDQJoHQCQC2CA",
    "H3D3Jo,D4SKH6CTS8SAS2CQH4HAC5DADKD9",
    "C3JoH3D3,S2S3H7HQCACTC2CKC6S7H5C7",
    "H5C5S5D5,C7S6D6C3H7HAH6H4C6HQC9",
    "H7S7C7D7,S5SAH5HAD5DAC5CA",
    "D4H4S4C4,S6SAH6HAD6DAC6CAJo",
    "DTCTSTHT,S3SQH3HQD3DQC3CQJo",
    "JoS8D8H8,S9DTH9CTD9STC9CAC2"
  ].each do |input|
    puts Daihinmin.play(input)
  end
end

カードをどう表現するか迷って、ここではクラス化しました(Cardクラス)。カードのランクを数値化し、弱い順に 0 からの Integer を与えてあります(Card#rank)。単独でいちばん強いジョーカーのランクは 13 です。String で表されるカードの表現はCard#valueで取得できます。

処理としては、まず場の札をtable、手札をhandとします。ジョーカーが存在すればjokerに入ります(存在しなければnil)。
手札をランクによってグループ化し、場の札のランク(t_rank)よりランクの高いものだけを残したのがcsです。

csから、場の札の枚数(t_num)以上のものを残し、あとは場の札の枚数だけの組み合わせを取ってresultに格納します。
手札にジョーカーがあり、かつ場の札の枚数(t_num)が2枚以上の場合は、ジョーカーをワイルドカードとしながらさらに同様のことをします。

結果を String で表現する(Array#join を使っています)ため、Card#to_sCard#valueのエイリアスとして設定しています。

なお、出力は順序の不定性があるので、テストをサボっています(笑)。

追記

テストも書いてみました。

if __FILE__ == $0
  def same?(input, expect)
    inputs = input.split(",")
    expects = expect.split(",")
    return false unless inputs.size == expects.size
    equal = ->(a, b) {
      a == b || a.scan(/../).sort == b.scan(/../).sort
    }
    is_found = ->(ans) {
      s = expects.find { |e| equal.(e, ans) }
      return false unless s
      expects.delete(s)
      true
    }
    inputs.all?(&is_found)
  end

  [
    ["DJ,", "-"],
    ["H7,HK", "HK"],
    ["S3,D4D2", "D4,D2"],
    ["S9,C8H4", "-"],
    ["S6,S7STCK", "CK,ST,S7"],
    ["H4,SAS8CKH6S4", "S8,CK,H6,SA"],
    ["ST,D6S8JoC7HQHAC2CK", "Jo,C2,CK,HA,HQ"],
    ["SA,HAD6S8S6D3C4H2C5D4CKHQS7D5", "H2"],
    ["S2,D8C9D6HQS7H4C6DTS5S6C7HAD4SQ", "-"],
    ["Jo,HAC8DJSJDTH2", "-"],
    ["S4Jo,CQS6C9DQH9S2D6S3", "DQCQ,D6S6,H9C9"],
    ["CTDT,S9C2D9D3JoC6DASJS4", "JoC2,SJJo,DAJo"],
    ["H3D3,DQS2D6H9HAHTD7S6S7Jo", "JoHA,JoD6,JoH9,D6S6,D7S7,JoS6,HTJo,JoDQ,S2Jo,JoD7,JoS7"],
    ["D5Jo,CQDAH8C6C9DQH7S2SJCKH5", "CQDQ"],
    ["C7H7,S7CTH8D5HACQS8JoD6SJS5H4", "HAJo,JoSJ,H8S8,H8Jo,CQJo,CTJo,JoS8"],
    ["SAHA,S7SKCTS3H9DJHJH7S5H2DKDQS4", "-"],
    ["JoC8,H6D7C5S9CQH9STDTCAD9S5DAS2CT", "CTDT,H9D9,S9D9,DACA,CTST,H9S9,DTST"],
    ["HTST,SJHJDJCJJoS3D2", "DJCJ,SJDJ,JoHJ,CJHJ,SJJo,HJSJ,DJJo,JoCJ,JoD2,SJCJ,DJHJ"],
    ["C7D7,S8D8JoCTDTD4CJ", "D8S8,JoS8,CTJo,DTJo,JoCJ,CTDT,D8Jo"],
    ["DJSJ,DTDKDQHQJoC2", "JoDK,HQDQ,DQJo,C2Jo,JoHQ"],
    ["C3H3D3,CKH2DTD5H6S4CJS5C6H5S9CA", "S5H5D5"],
    ["D8H8S8,CQHJCJJoHQ", "JoCQHQ,JoHJCJ"],
    ["H6D6S6,H8S8D8C8JoD2H2", "D2H2Jo,D8JoS8,D8S8C8,C8D8H8,JoC8S8,H8JoC8,S8H8C8,JoS8H8,C8JoD8,D8H8S8,D8JoH8"],
    ["JoD4H4,D3H3S3C3CADASAD2", "DACASA"],
    ["DJHJSJ,SQDQJoHQCQC2CA", "SQJoCQ,DQCQJo,JoSQHQ,SQCQHQ,DQHQSQ,HQDQCQ,HQDQJo,SQDQCQ,CQJoHQ,SQJoDQ"],
    ["H3D3Jo,D4SKH6CTS8SAS2CQH4HAC5DADKD9", "HASADA"],
    ["C3JoH3D3,S2S3H7HQCACTC2CKC6S7H5C7", "-"],
    ["H5C5S5D5,C7S6D6C3H7HAH6H4C6HQC9", "C6D6S6H6"],
    ["H7S7C7D7,S5SAH5HAD5DAC5CA", "SADACAHA"],
    ["D4H4S4C4,S6SAH6HAD6DAC6CAJo", "C6H6S6D6,SAJoDACA,S6H6C6Jo,SACAJoHA,HADASAJo,HADAJoCA,CADAHASA,D6C6JoH6,S6D6C6Jo,H6JoS6D6"],
    ["DTCTSTHT,S3SQH3HQD3DQC3CQJo", "HQSQJoDQ,SQCQDQJo,DQCQHQJo,SQHQJoCQ,CQDQHQSQ"],
    ["JoS8D8H8,S9DTH9CTD9STC9CAC2", "H9C9D9S9"],
  ].each do |input, expect|
    p same? Daihinmin.play(input), expect
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby】RubyでGmailを操作する基礎

出涸らし情報だけどRuby教えるときに便利なので手順メモ

Gmailのアプリパスワードを取得する

要は以下のような16桁が欲しい
スクリーンショット 2021-01-10 9.03.09.png

以下作業はこちらの画面で行います。

  1. Google 二段階認証を有効にする (これやらないとパスワードが取れない!)
    • [パスワードとログイン方法] -> [2段階認証プロセス]
    • SMSで認証して有効化させる
  2. アプリパスワードを取得する
    • [パスワードとログイン方法] -> [アプリパスワード]
    • [アプリを選択]と[デバイスを選択]をそれぞれ入力 (本人が管理できればなんでもいい。。はず)
    • アプリパスワードが生成されるので控えておく

gem mail をインストール

$ gem mail install

サンプルコードを書く

send_gmail.rb
require 'mail'

from   = 'my_gmail_address@gmail.com'
password = '16_digit_app_password'
to = 'to_address@gmail.com'

Mail.defaults do
  delivery_method :smtp, {
    address: 'smtp.gmail.com',
    port: 587,
    domain: 'example.com',
    user_name: from,
    password: password,
    authentication: :login,
    enable_starttls_auto: true
  }
end

m = Mail.new do
  from "#{from}"
  to "#{to}"
  subject "Great Mail Title"
  body "Fantastic body blar blar"
end

m.charset = "UTF-8"
m.content_transfer_encoding = "8bit"
m.deliver

取り敢えずこれで動くはず。

参考情報

Ruby を使って Gmail 経由でメール送信
http://1bed.allright.life/?p=2004

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RubyでGmailを送信する基礎

出涸らし情報だけどRuby教えるときに便利なので手順メモ

Gmailのアプリパスワードを取得する

要は以下のような16桁が欲しい
スクリーンショット 2021-01-10 9.03.09.png

以下作業はこちらの画面で行います。

  1. Google 二段階認証を有効にする (これやらないとパスワードが取れない!)
    • [パスワードとログイン方法] -> [2段階認証プロセス]
    • SMSで認証して有効化させる
  2. アプリパスワードを取得する
    • [パスワードとログイン方法] -> [アプリパスワード]
    • [アプリを選択]と[デバイスを選択]をそれぞれ入力 (本人が管理できればなんでもいい。。はず)
    • アプリパスワードが生成されるので控えておく

gem mail をインストール

$ gem mail install

サンプルコードを書く

send_gmail.rb
require 'mail'

from   = 'my_gmail_address@gmail.com'
password = '16_digit_app_password'
to = 'to_address@gmail.com'

Mail.defaults do
  delivery_method :smtp, {
    address: 'smtp.gmail.com',
    port: 587,
    domain: 'example.com',
    user_name: from,
    password: password,
    authentication: :login,
    enable_starttls_auto: true
  }
end

m = Mail.new do
  from "#{from}"
  to "#{to}"
  subject "Great Mail Title"
  body "Fantastic body blar blar"
end

m.charset = "UTF-8"
m.content_transfer_encoding = "8bit"
m.deliver

取り敢えずこれで動くはず。

参考情報

Ruby を使って Gmail 経由でメール送信
http://1bed.allright.life/?p=2004

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rake routes をしたら、怒られた。yarnのバージョンが低いってばよ

rake routes
error Couldn't find an integrity file                                                                                                                                                              
error Found 1 errors.                                                                                                                                                                              


========================================
  Your Yarn packages are out of date!
  Please run `yarn install --check-files` to update.
========================================


To disable this check, please change `check_yarn_integrity`
to `false` in your webpacker config file (config/webpacker.yml).


yarn check v1.22.10
info Visit https://yarnpkg.com/en/docs/cli/check for documentation about this command.


koyoishikawa@ishikawyounoAir Ruby_on_rails_task_2 % yarn install --check-files  
yarn install v1.22.10
[1/4] ?  Resolving packages...
[2/4] ?  Fetching packages...
[3/4] ?  Linking dependencies...
warning " > webpack-dev-server@3.11.0" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
warning "webpack-dev-server > webpack-dev-middleware@3.7.2" has unmet peer dependency "webpack@^4.0.0".
[4/4] ?  Building fresh packages...
✨  Done in 341.25s.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RSpecの導入&関係の深いgemについて

Ruby on Railsのテスト用のgemであるRSpecの導入と関係するgemについて整理します。

RSpecとは

Ruby on Railsのテストコードを書くために用いられるgemです。
実際のgem名は「rspec-rails」です。

ユーザーの新規登録やログイン、メッセージの投稿や編集などの機能をテストするために使用します。

Rubyには標準でmini_testという別のテスト用Gemが導入されているのですが、開発現場ではRSpecが主流だそうです。

RSpecのインストール

Gemfileに以下のように記述し、ターミナルでbundle installを入力します。

Gemfile
#group :development, :testというグループの中に記述する。
group :development, :test do
  gem 'rspec-rails', '~> 4.0.0'
end
ターミナル
bundle install

RSpecの設定をする

ターミナルで以下のとおり入力して、ディレクトリとファイルを生成します。

ターミナル
rails g rspec:install
#上記実行後、下記のディレクトリとファイルが生成される
    create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

.rspecファイルに以下の記述をします。テストコードの実行結果をターミナル上に可視化するために記述します。

.rspec
--require spec_helper
--format documentation #この一文を追加

RSpecについてはここまです。

RSpecと関係の深いgem

RSpecとよく一緒に使用するgemとしてFactoryBotとFakerがあります。

FactoryBotとは

インスタンスをまとめることができるgemで、他のファイルであらかじめ各クラスのインスタンスに定める値を設定しておき、各テストコードで使用します。

RSpecと同様にGemfileのgroup :development, :testというグループの中に記述してからインストールします。

Gemfile
group :development, :test do
  gem 'factory_bot_rails'
end
ターミナル
bundle install

簡単な紹介になりますが、FactoryBotは以下のように記述して使います。※アプリにUsersテーブルがあると想定

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name {"sample"}
    email {"sample@mail"}
    password {"sample777"}
  end
end

このように記述することで、テストで使用するユーザーの情報を事前に用意しておくことができます。

Fakerとは

ランダムな値を生成するgemで,メールアドレス、人名、パスワードなどのランダムな値を生成してくれます。
RSpecと同様にGemfileのgroup :development, :testというグループの中に記述してからインストールします。

Gemfile
group :development, :test do
  gem 'faker '
end
ターミナル
bundle install

Fakerはこんな感じでランダムな値を生成します。

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name { Faker::Name.initials(number: 5) }
    email { Faker::Internet.free_email }
    password { Faker::Internet.password(min_length: 6) }
  end
end

FactoryBotとFakerでできること

テストコードを実行するたびにランダムな値でインスタンスを生成することができます。
テストをするアプリにユーザー管理機能がある場合、emailなどのカラムをテーブルに用意されていると思いますが、複数のテストを行う際に、「すでにemailの重複したインスタンスが存在する」といったような意図しない形でテストエラーが出てしまうことを防ぐのに役立ちます。

今回はここまでで、次の記事でRSpecを使った単体テストについて取り上げたいと思います。

参考資料

RSpecの公式GitHub
FactoryBot(factory_bot_rails)の公式GitHub
Fakerの公式GitHub

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む