20201108のRubyに関する記事は27件です。

Leet文字列(ruby編)

Leet文字列(ruby編)

先に言っておくと、期待通りの出力ができなかったので分かる方教えて頂けると嬉しいです?

問題

Leet ではいくつかのアルファベットをよく似た形の他の文字に置き換えて表記します。 Leet の置き換え規則はたくさんありますが、ここでは次の置き換え規則のみを考えましょう。
置き換え前 置き換え後
A 8
E 3
G 6
I 1
O 0
S 5
Z 2

文字列が入力されるので、これを Leet に変換して出力するプログラムを書いてください。

入力される値

入力は以下のフォーマットで与えられます。
i
i は Leet に変換する前の文字列を表します。

期待する出力

i を Leet に変換した文字列を1行に出力してください。

入力例1

APPLE

出力例1

8PPL3

私の答え(誤った答えです)

ruby
array = {
"A" => "8",
"E" => "3",
"G" => "6",
"I" => "1",
"O" => "0",
"S" => "5",
"Z" => "2"
}
str = gets.gsub(/[A-Z]/, array)
print str

悩んだポイント

今回のポイントは「アルファベットが当てはまったら該当のアルファベットのみ数字に置換する」という事でした。ですが、gsub(/[A-Z]/)でこのままのコードでは出力時に83のみ出力されてしまいます。(おそらく条件分岐でelseを用いて当てはまらなった部分を出力できていないから。)8PPL3と出力する際の正規表現なのかそもそものgsubメソッド以外に適したメソッドがあるのか分かりませんでした。ちなみに下記に条件分岐での手段も試してみましたがうまくいきませんでした。

str = gets.chomp.split(" ")
str.each { |s|
case s
when "A"
  print "8"
when "E"
  print "3"



(省略)
else
  print s
}

この問題の8PPL3という出力結果にする方法は様々あると思いますが当てはまったら部分的に置換するという方法がこれ以上できませんでした。もし分かる方、お優しい方がいらっしゃいましたらご教授頂けますと幸いです。

以上

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

Rails6でRatyを使用して星評価機能を実装する

rails6でratyを使用して星での評価機能を実装。

まず、Jqueryを入れる
https://qiita.com/masahisa/items/eaacb0c3b82f4a11fc13

これを参考にjqueryを導入してください。
rails6になり、
$ yarn add jquery の記述が忘れがちなので注意が必要です。

githubから星imageを保存
https://github.com/wbotelhos/raty/tree/master/lib/images
これを app/assets/imagesに保存。

同じくgithubからjavascriptコードをコピー
rails6では app/assets/javascripts というフォルダがないので、
app/assets に javascriptsというフォルダを作成。
配下にaplication.jsファイルも作成、そこにgithubからコピーしたコードを全て貼り付け。

app/assets/config/manifest.jsに

//= link_directory ../javascripts .js

を追加。

app/layouts/application.html.erbに

<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>

を追加。

ここまでが環境設定。rails6以外では大きくやり方が異なるので注意下さい。
(6でもこのやり方が正解かは不明ですが、一応これで読み込むことはできました。)

以下機能の実装。

rateカラムを追加
※float型を推奨。星半分でも反映できる為。
私の場合はevaluationsテーブルに作成。投稿した記事の評価を星で実施。

rails g migration AddRateToEvaluations rate:float
          ↓
rails db:migrate

星評価機能を付けたいviewのformに

  <div class="form-group row" id="star">
    <%= f.label :rate,'総合評価', class:'col-md-3 col-form- 
    label' %>
    <%= f.hidden_field :rate, id: :review_star %>
  </div>

  <!-- 評価javascript -->
  <script>
  $('#star').raty({
    size     : 36,
    starOff:  '<%= asset_path('star-off.png') %>',
    starOn : '<%= asset_path('star-on.png') %>',
    starHalf: '<%= asset_path('star-half.png') %>',
    scoreName: 'evaluation[rate]',
    half: true,
  });
  </script>  

を記述。ポイントはhidden_fieldでidにreview_starを定義すること。
Js部分で保存した星の画像を読み込み。

コントローラでrateパラメータをpermitするのも忘れずに。
これで星で入力できる状態になるはずです。

一応、入力したデータを表示したコードも記載しておきます。
私の場合は、evaluationsテーブルにcommentカラムがあったり、postモデル、userモデルとアソシエーションが組まれている形なので、参考程度でお願い致します。

   <tbody>
    <% @evaluations.each do |evaluation| %>
    <tr>
     <td><%= evaluation.comment %></td>
     <td>
     <a href="/users/<%= evaluation.user.id %>">
    <%= evaluation.user.nickname %>
   </a></td>

      <!--星評価-->
    <td><div id="star-rate-<%= evaluation.id %>"></div>
      <script>
      $('#star-rate-<%= evaluation.id %>').raty({
        size: 36,
        starOff:  '<%= asset_path('star-off.png') %>',
        starOn : '<%= asset_path('star-on.png') %>',
        starHalf: '<%= asset_path('star-half.png') %>',
        half: true,
        readOnly: true,
        score: <%= evaluation.rate%>,
      });
      </script> </td>

      <% end %>
    <!--/星評価-->


  </tr>


以上です。

https://gyazo.com/4e0cd5b4b5dc11d134bc65e6a7b7db74

こんなイメージです。rails6の記事がなく、6以外の記事を参考に無理やり作った形ですので、正解は違うと思います。

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

既存のRailsアプリのローカル環境にDockerを導入する方法【Rails6 / MySQL8】

はじめに

本記事は私が既存のポートフォリオアプリのローカル環境にDockerを導入した際の手順になります。
Dockerの公式ドキュメントはPostgreSQLでの手順解説になっているためMySQLになると公式ドキュメントの手順解説はなく、
また、Rails6での情報もRails5と比べるとやはり少なく、結果、Rails6MySQLという組み合わせでの情報がとても少なかったです。
そのため、私自身様々なエラーが出て試行錯誤しましたので、本記事がこれからRails6MySQLという組み合わせでDockerを導入する方の助けになれば幸いです。
なお、筆者は就職活動中の実務未経験の初学者のため、誤っている点などあるかもしれません。
その際はコメントにて教えて頂けると幸いです。

前提条件

  • Mac版 DockerDesktopのインストールが完了していること
  • Dockerが起動済みであること

※筆者の場合、上記インストールと起動は「ゼロからはじめるDockerによるアプリケーション実行環境構築」を観ながらやりました。
Dockerの全体像を学ぶ上でもとてもわかりやすく、おすすめです。

バージョン

  • Ruby 2.6.5
  • Rails 6.0.3.2
  • MySQL 8.0.21

手順1 Dockerfile作成

既存アプリケーションのルートディレクトリにDockerfileを作成し、下記のように記述します。

※今回はアプリのディレクトリ名をsample_appとします。
ここはあなたのアプリのディレクトリ名に置き換えてください。

Dockerfile
FROM ruby:2.6.5
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs yarn
WORKDIR /sample_app
COPY Gemfile ./Gemfile
COPY Gemfile.lock ./Gemfile.lock
RUN gem install bundler
RUN bundle install
COPY . /sample_app
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000
CMD ["rails", "server", "-b", "0.0.0.0"]

手順2 docker-compose.yml作成

こちらも手順1と同じく、既存アプリケーションのルートディレクトリにdocker-compose.ymlを作成し、下記のように記述します。

docker-compose.yml
version: '3' #docker-composeのバージョン
services:
  db:
    image: mysql:8.0.21 #既存アプリとあわせる。ターミナルに[$ mysql --version]で確認
    environment:
      MYSQL_ROOT_PASSWORD: vxgbizakqc #あなたのパスワード
      MYSQL_DATABASE: root
    ports:
      - "4306:3306"
    volumes:
      - ./mysql-confd:/etc/mysql/conf.d
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/sample_app
    ports:
      - 3000:3000
    depends_on:
      - db
    tty: true
    stdin_open: true
volumes:
  mysql-data:

手順3 entrypoint.sh作成

こちらも手順1・手順2と同じく、既存アプリケーションのルートディレクトリにentrypoint.shを作成し、下記のように記述します。

entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /sample_app/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

手順4 config/database.ymlの編集

configディレクトリにあるdatabase.ymlを下記のように編集します。

config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: vxgbizakqc #あなたのパスワード
  socket: /tmp/mysql.sock
  host: db

development:
  <<: *default
  database: sample_app_development

test:
  <<: *default
  database: sample_app_test

手順5 Dockerイメージの作成

ターミナルで下記のコマンドを実行し、Dockerイメージを作成します。

ターミナル
MacBook-Pro:sample_app$ docker-compose build

手順6 Dockerコンテナを起動

ターミナルで下記のコマンドを実行し、Dockerコンテナを起動します。

ターミナル
MacBook-Pro:sample_app$ docker-compose up

手順7 コンテナ内にデータベースを作成する

ターミナルで下記のコマンドを実行し、コンテナ内にデータベースを作成します。

ターミナル
MacBook-Pro:sample_app$ docker-compose run web rails db:create

上のコマンドを実行すると先ほど編集したdatabase.ymlを元にデータベースを作成します。
データベースの作成が成功すると下記のように表示されます。

ターミナル
reating sample_app_web_run ... done
Created database 'sample_app_development'
Created database 'sample_app_test'

手順8 作成したデータベースのマイグレーションを実行

ターミナルで下記のコマンドを実行し、データベースのマイグレーションを実行します。

ターミナル
MacBook-Pro:sample_app$ docker-compose run web rails db:migrate

データベースのマイグレーションが成功すると下記のように表示されます。

ターミナル
Creating sample_app_web_run ... done
== 20209165960112 DeviseCreateUsers: migrating ================================
-- create_table(:users)
   -> 0.0253s

  #中略

-- add_index(:notifications, :comment_id)
   -> 0.0251s
== 20209165960112 CreateNotifications: migrated (0.1280s) =====================

手順9 Gemのインストール

さぁ、長かったDocker導入もこれでいよいよラストです。
ターミナルで下記のコマンドを実行し、Gemをインストールします。

ターミナル
MacBook-Pro:sample_app$ docker-compose exec web bundle install

以上です!!
localhost:3000に接続するとアプリケーションが表示されています。

参考文献

今回は以下の記事や動画教材を参考にさせて頂いた結果Dockerを導入することができました。
記事を書いて下さった方々、動画教材作成者の小島様、ありがとうございます。

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

【Ruby】メソッドの引数の個数についての疑問と検証

背景

method1 a b, c

となっている場合、cはどのメソッドの引数になっているでしょうか?

Ruby ってメソッドの引数にカンマやカッコをつけなくても良いのです。
ただ、このシステムの場合、不具合が起こると思うのです。
そこで、今回はそれを実際に試してみて検証しました。
下記の疑問について、なかなか記事がなくて不思議に思っていました。

問題がありそうな場面 

例えば、

  • パターン1
method1 a, b

method1(a, b)

という意味ですよね(methodって名前でメソッドを定義しようとしたらエラーになったので、method1としています。。)。

  • パターン2
method1 a b

method1(a(b))

という意味ですよね。
ここまでは良いです(この時点で既にちょっと違和感がありますが。。初心者にはきついです。。)

ここで疑問

method1 a b, c

って、2つ可能性がないでしょうか?

  • 可能性1
method1(a(b),c) 

  • 可能性2
method1(a(b,c)) 

の両方の可能性です。

  • 前者はcがmethod1の第二引数 で、
  • 後者はcがaの第二引数です。

ここで、確認するべきなのは、

  • method1 のとる引数の数 と
  • a のとる引数の数

です。単純にこれだけは確認する必要があります。(この時点で読む側からしてはきついです。)
ただ、可変長引数の場合はどうするのでしょうか?
おそらく、どちらかが優先されるのですが。。

では、上記のどちらのパターンになるのか検証してみます。

検証

def a *temp
  p "aの引数の個数は"
  p temp.size
end

def method1 *temp
  p "method1の引数の個数は"
  p temp.size
end

結果は、、、

method1 a 1, 2
# "aの引数の個数は"
# 2
# "method1の引数の個数は"
# 1

つまり、上記は

method1(a(1, 2))

と受け取られました。
上記の可能性2が採用されているようです。

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

Ruby on Rails 基礎

Rubyと同じく個人用

間違っていたら教えて頂きたい。

開発環境

AWS Cloud9

Ruby on Railsスタート

基本的にはLaravelと同じでMVCの考え方で大丈夫らしい。

環境構築

$ gem install rails -v 5.2.4
$ rails new app_name

コントローラ作成

コントローラ名はなるべく複数形
てかだいたい複数形

場所は
~/home/ec2-user/environment/アプリ名/app/controllers

$ rails g controller コントローラ名

コントローラアクション付きコマンド

$ rails g controller コントローラ名 アクション名
$ rails g controller todolists home

コントローラ削除

$ rails d controller コントローラ名

ルーティング設定

ルーティングに対しての考え方はLaravelと同じ

結構何でも支配している、司令室みたいなところ

Rails.application.routes.draw do
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  get 'home' => 'tops#home'
#URLでhomeのところに来たら、 topsコントローラのhomeアクションをかますよ!って意味
end

ビュー設定

~/app/view/名前のところに先ほど指定したファイル(home.html.erb)を作ればいいだけ

htmlやら何やらのUIは頑張るしかない

サーバー起動方法

下記のどっちか

$ rails server
 $ rails s

エラーメッセージについてはいつかまとめる。

モデル作成

データベース関連と一瞬で繋がっちゃうすごいやつ。

詳しくは調べてみてくださいね(フレームワークの醍醐味だから)

$ rails g model モデル名

単数形、最初大文字

4つファイルが出てきたらOK

マイグレーションファイル作成

もうLaravelではおなじみのアレ

テンプレート的なのを貼っておきます

class CreateLists < ActiveRecord::Migration[5.2]
  def change
    create_table :lists do |t|
      t.string :name
      t.string :contents
      t.timestamps
    end
  end
end

マイグレートする

マイグレーションファイルにあるものをデータベースに反映するための呪文

$ rails db:migrate

マイグレート後のカラム追加

$rails g migration Addカラム名Toテーブル名 カラム名:型名
$rails g migration AddIdToLists Id:int

マイグレート後のカラム削除

$rails g migration Removeカラム名Fromテーブル名 カラム名:型名
$rails g migration RemoveIdFromLists Id:int

まぁ英語を考えればわかりやすいよな

でもここまでやって俺は思った。
データベースって作成したっけ??
あれれれれ?

あとあと調べてみると、僕が普段使っていたMySQLではなくRubyonRailsはデフォルトでSQLiteを使用しているらしい…(道理でMysql探しても見当たらなかったわけだ)

ちなみにデータベースは

$rake db:create

これでできるらしい

とはいえまだまだ謎だらけ、勢いあまりすぎたかな…?

随時更新していきますね。

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

Ruby 基礎

この記事は完全に個人のRuby開発の際に使用するためだけに作っている。
参考にしていただいてもいいのだが、あくまで個人用という事だけわかって頂きたい。
*執筆者はPHP学習済みなので、飛ばす部分があるが大目に見て欲しい

開発環境

AWS cloud9
理由:パソコンにもうファイルが入れられないし、USBなど外部に入れるとあとあとメンドくさい可能性があるから

Rubyメモ開始

まつもとゆきひろ氏が作った言語。
最近ベンチャー企業とかでめちゃくちゃ使われている。
比較的書きやすい言語

環境構築

$ gem install rails -v 5.2.4
$ rails -v
Rails 5.2.4

Ruby基礎文法

出力

puts 'hello world'
p "hello world"
#hello world

型についてはPHPとほぼ同じ。

だが妙なものがたくさんある

puts "kaiseiblogの年齢は" + 20 + "才です"
#エラー
puts "kaiseiblogの年齢は" + 20.to_s + "才です"
#kaiseiblogの年齢は20です

#整数型→文字列型(多分toStringって意味)
puts puts 10 + "10"
#エラー
puts puts 10 + "22".to_i
#kaiseiblogの年齢は20です
#32

#文字型→整数列型(tointですね笑)

文字数数え(length)

puts "kaisei".length
#6

文字列逆(reverse)

puts "kaiseiblog".reverse
#golbiesiak

変数

基本はPHPと一緒

定数

効果はPHPと同じ

アルファベット大文字で始まる識別子は定数

Kaisei = "ばか"
puts Kaisei
#ばか

変えようとするとエラーは出るけど出ちゃうらしい…

四則演算ほか

数字を文章と繋げて出力したいときは、先ほどのto_sをつける

それがめんどくさい時は以下のものが使える

変数展開

name = "kaiseiblog"
pv = 8000

puts name + "は" + pv.to_s + "pvの閲覧数がある"
#↓この書き方
puts "#{name}#{pv}kgです" 

注意!!!!:""では使えるけど、''では使えない!

配列

PHPとめちゃくちゃ似ていてビッくらポン

blog = ["kaiseiblog", "sabichou", "siva"]
puts names[1]
#sabichou

言うことなしですね

ハッシュやらシンボルやら

age = {"kaiseiblog"=>20, "daniel"=>48, "roland"=>90}
puts age["kaiseiblog"]
#20
age = {:kaiseiblog=>20, :daniel=>48, :roland=>90}
puts age[:kaiseiblog]
#20

if文

なんか後にコロンが書かなくて良いとかいう優しさを見せるRuby

kaiseiblog_pv = 8000
if kaiseiblog_pv < 5000
  puts "アマチュアブロガー"
end

if kaiseiblog_pv >= 5000  
  puts "プロブロガー"
end  
kaiseiblog = "Kaisei"

if kaiseiblog == "Abe"
  puts "この人のブログはAbemaです。"
elsif kaiseiblog == "Kaisei"
  puts "この人のブログはkaiseiblogです。"
else
  puts "誰だお前"
end

input系

Rubyはユーザーの入力にinput系の関数を使わないらしい

getsってやるぽいですね。

puts "あなたの名前は?"
input_key = gets
puts "あなたの名前は#{input_key}"
#あなたの名前は(入力したもの)

while系

pv = 8000  

while pv < 10000 do  
  pv += rand(1000..6000) 
  puts pv
end
#10725

さりげなくランダムも学ぶスタイル

繰り返し

ややPythonと似てるかな

for i in 1000..8000 do  
  puts i
end

?絶対にこれをコピペしないほうがいい。
ターミナルが埋まった

配列オブジェクト取り出し

なんかPythonで似たのあったよね

amounts = {"kaisei"=>20, "Robert"=>35, "Michel"=>73}
amounts.each do |people, age|  
  puts "#{people}#{age}才です。"
end

繰り返し中断

お馴染みですね

kaisei_age = 0
while kaisei_age <= 100 do
  if kaisei_age == 20
    puts "現在になりました"
    break  
  end
  puts kaisei_age
  kaisei_age += kaisei_age
end

メソッド(関数)

returnとか書かなくていいの?(歓喜)
と思ってたらやっぱりダメだった(二文以上書く場合は、return書かないと最後のやつしか反映されない)

def call(name)
  "もしもし! #{name} さん!"
end

puts call("kaisei")
def call(name)
  return "もしもし! #{name} さん!"
  "おい!#{name}!"
end

puts call("kaisei")
#もしもし!kaiseiさん!

オブジェクト指向

うわ…だるいのきたよ
(好きだけど)

見た所、ほぼ今までのPythonやPHPと同じ

class Blog


    attr_accessor :name, :pv, :ctr

    def initialize
        self.name = "WordPress"
        self.pv = 200
        self.ctr = 20
    end


    def write(article_keyword)
        puts "#{self.name}#{article_keyword}の記事を更新して#{self.pv}回閲覧されました。"
    end

end


kaiseiblog = Blog.new

kaiseiblog.write("プログラミング")


多分色々コンストラクトとかあると思うが省略。

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

net/httpを利用した外部API連携クラスの作成手順

アプリケーション開発をするにあたり、外部サービスとAPI連携を行う場合があります。

RubyではHTTP通信を行う標準ライブラリとしてnet/httpが用意されています。
今回はnet/httpを利用して、API経由で外部サービスのデータを取得する方法について紹介します。

今回作成するもの

今回は外部サービスの例としてQiitaを利用します。

ゴールは以下の通りです。

  • QiitaClientAPIというQiita APIと連携するクラスを作成する
  • get_itemsというクラスメソッドを利用することでQiitaの記事一覧が取得できるようにする
  • API連携でエラーが発生した場合は、エラーレスポンスの内容を例外として出力する
  • localhost:3000/qiita_itemsを実行するとQiitaClientAPI.get_itemsの結果が取得できる

なお、今回紹介するサンプルはrails 6.0.3.4のAPIモードで作成しています。

最終的な成果物

今回は手順を追いながらソースコードの紹介をしていきます。
最終的なアウトプットは以下のようになります。

ソースコード

config/routes.rb
Rails.application.routes.draw do
  resources :qiita_items, only: %i(index)
end
app/controllers/qiita_items_controller.rb
class QiitaItemsController < ApplicationController
  def index
    response_json = QiitaApiClient.get_items

    # レスポンスを簡略化するため、titleプロパティのみ返すようにしている
    render json: response_json.map {|item| item.slice('title') }
  end
end
lib/qiita_api_client.rb
class QiitaApiClient
  class HTTPError < StandardError
    def initialize(response)
      super "code=#{response.code} body=#{response.body}"
    end
  end

  def initialize
    @token = Rails.application.credentials.qiita[:token]
  end

  def get_items
    request = Net::HTTP::Get.new(
      '/api/v2/items',
      'Authorization' => "Bearer #{@token}"
    )
    http_client.request(request)
    response = http_client.request(request)
    case response
    when Net::HTTPSuccess
      JSON.parse(response.body)
    else
      raise QiitaApiClient::HTTPError.new(response)
    end
  end

  class << self
    def client
      QiitaApiClient.new
    end

    def get_items
      client.get_items
    end
  end

  private

  QIITA_HOST = 'https://qiita.com'

  def http_client
    uri = URI.parse(QIITA_HOST)
    http_client = Net::HTTP.new(uri.host, uri.port)
    http_client.use_ssl = true
    http_client
  end
end

実行結果

API連携が成功した場合は以下のような結果になります。

$ curl 'http://localhost:3000/qiita_items'

[{"title":"highlight.jsを動的に使ってみた - CodePen"},{"title":"飛び飛びセル順次コピペ"},{"title":"テキスト入力中の点滅するカーソルに好きなCSSを当てる方法"},{"title":"jQueryいろいろ(wrapAll, MutationObserverなど)"},{"title":"高機能なSQL開発ツール「A5:SQL Mk-2」をUbuntuで使う"},{"title":"Amazon Aurora カスタムエンドポイントの検証と考察"},...
()
...
]

API連携が失敗した場合は以下のような結果になります。

### 不正なトークンが利用されている場合
$ curl 'http://localhost:3000/qiita_items'

QiitaApiClient::HTTPError (code=401 body={"message":"Unauthorized","type":"unauthorized"})

下準備

実装をするにあたり、Qiita APIと連携するための準備をします。

アクセストークンの取得

Qiita APIの認証認可に必要なアクセストークンを取得します。

アクセストークンはユーザの管理画面で取得できます。

なおQiita APIのGETリクエストではアクセストークンは不要なため1、今回紹介する記事一覧取得API(/api/v2/items)のみを実装したい場合はこの作業は不要です。

アクセストークンをRailsアプリケーションに登録する

今回はRails 6のcredentialsにトークンを保存しました。

### config/credentials/development.yml.encの編集
$ export EDITOR="vim"
$ rails credentials:edit -e development
→ このタイミングでconfig/credentials/development.keyとconfig/credentials/development.yml.encが作成される
config/credentials/development.yml.enc
qiita:
  # 取得したトークンをセットする
  token: xxxxxxx
$ rails c

### 取得したトークンが表示されればOK
> Rails.application.credentials.qiita[:token]
=> 'xxxxxxx'

ルーティングの追加

検証で利用するlocalhost:3000/qiita_itemsのエンドポイントを作成します。

config/routes.rb
Rails.application.routes.draw do
  resources :qiita_items, only: %i(index)
end

lib配下のクラスを読み込むようにする

今回はQiita APIと連携するクラスをlib配下に作成します。
lib配下のクラスが読み込まれるようにするため以下のように修正します。

config/application.rb
module RailsApiClient
  class Application < Rails::Application
    # 以下を追加
    config.paths.add 'lib', eager_load: true
  end
end

net/httpを利用した外部APIとの連携方法

ここからは順を追って実装について紹介していきます。

シンプルな方法

Qiita APIと連携するクラスを作成せず、ロジック2を直接記述するパターンです。

app/controllers/qiita_items_controller.rb
class QiitaItemsController < ApplicationController
  def index
    uri = URI.parse('https://qiita.com')
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    request = Net::HTTP::Get.new(
      '/api/v2/items',
      'Authorization' => "Bearer #{Rails.application.credentials.qiita[:token]}"
    )
    response = http.request(request)
    response_json = JSON.parse(response.body)

    # レスポンスを簡略化するため、titleプロパティのみ返すようにしている
    render json: response_json.map {|item| item.slice('title') }
  end
end

ソースコードを見ればロジックはわかると思いますが、net/httpを利用したGETリクエストの流れについて改めてまとめると以下のようになります。

  1. Net::HTTP.newでHTTPのクライアントのオブジェクトを作成
  2. Net::HTTP::Get.newでGETリクエストのオブジェクトを作成
  3. HTTPクライアントを利用してGETのリクエスト

Qiita APIと連携する専用クラスを作成する

QiitaApiClient.get_itemsを呼ぶことでデータが取得できるようにします。
lib配下にQiitaApiClientクラスを作成し、API連携のロジックを移行します。

lib/qiita_api_client.rb
class QiitaApiClient
  class << self

    QIITA_HOST = 'https://qiita.com'

    def get_items
      uri = URI.parse(QIITA_HOST)
      http = Net::HTTP.new(uri.host, uri.port)
      http.use_ssl = true # SSLを有効化する
      request = Net::HTTP::Get.new(
        '/api/v2/items', # 記事一覧を取得するエンドポイント
        'Authorization' => "Bearer #{Rails.application.credentials.qiita[:token]}" # Bearer認証
      )
      response = http.request(request)
      JSON.parse(response.body)
    end
  end
end

API連携のロジックをQiitaApiClientに移行したので呼び出す側は以下のようになります。

app/controllers/qiita_items_controller.rb
class QiitaItemsController < ApplicationController
  def index
    response_json = QiitaApiClient.get_items
    render json: response_json.map {|item| item.slice('title') }
  end
end

リファクタ: Net::HTTPオブジェクトの作成を共通化する

このままでも問題ないのですが、クラスメソッド(今回でいうget_items)でNet::HTTP.newを実行しているため、クラスメソッドを追加するたびにNet::HTTP.newも追加されてソースコードが少し冗長になります。

そこで、Net::HTTP.newを実行するインスタンスメソッドを作成します。

lib/qiita_api_client.rb
class QiitaApiClient
  def initialize
    # トークンはインスタンス変数として呼び出せるようにする
    @token = Rails.application.credentials.qiita[:token]
  end

  def get_items
    request = Net::HTTP::Get.new(
      '/api/v2/items',
      'Authorization' => "Bearer #{@token}"
    )
    # self.http_clientを呼び出す
    response = http_client.request(request)
    JSON.parse(response.body)
  end

  private

  QIITA_HOST = 'https://qiita.com'

  def http_client
    uri = URI.parse(QIITA_HOST)
    http_client = Net::HTTP.new(uri.host, uri.port)
    http_client.use_ssl = true
    http_client
  end
end

QiitaApiClientを呼び出す側は以下のようになります。

app/controllers/qiita_items_controller.rb
class QiitaItemsController < ApplicationController
  def index
    qiita_client = QiitaApiClient.new
    response_json = qiita_client.get_items
    render json: response_json.map {|item| item.slice('title') }
  end
end

リファクタ: QiitaApiClientインスタンスを呼び出し側で作成しなくて済むようにする

インスタンスを作成しなくてもQiitaApiClient.get_itemsを実行するだけでAPI連携できるようにリファクタリングした結果は以下のとおりです。

lib/qiita_api_client.rb
class QiitaApiClient
  def initialize
    @token = Rails.application.credentials.qiita[:token]
  end

  def get_items
    request = Net::HTTP::Get.new(
      '/api/v2/items',
      'Authorization' => "Bearer #{@token}"
    )
    response = http_client.request(request)
    JSON.parse(response.body)
  end

  class << self
    def client
      QiitaApiClient.new
    end

    def get_items
      client.get_items
    end
  end

  private

  QIITA_HOST = 'https://qiita.com'

  def http_client
    uri = URI.parse(QIITA_HOST)
    http_client = Net::HTTP.new(uri.host, uri.port)
    http_client.use_ssl = true
    http_client
  end
end

これで、以下のコードでQiita APIからデータを取得できるようになりました。

app/controllers/qiita_items_controller.rb
class QiitaItemsController < ApplicationController
  def index
    response_json = QiitaApiClient.get_items
    render json: response_json.map {|item| item.slice('title') }
  end
end

例外処理の追加

Qiita APIのレスポンスがエラーだった場合、APIのエラーの内容がわかるよう例外処理を追加します。
今回はエラー時のレスポンスの内容とエラーコードを例外のメッセージに追加しました。

lib/qiita_api_client.rb
class QiitaApiClient
  def initialize
    @token = Rails.application.credentials.qiita[:token]
  end

  def get_items
    request = Net::HTTP::Get.new(
      '/api/v2/items',
      'Authorization' => "Bearer #{@token}"
    )
    response = http_client.request(request)
    case response
    when Net::HTTPSuccess
      JSON.parse(response.body)
    else
      raise "code= #{response.code}, body = #{response.body}"
    end
  end

  class << self
    def client
      QiitaApiClient.new
    end

    def get_items
      client.get_items
    end
  end

  private

  QIITA_HOST = 'https://qiita.com'

  def http_client
    uri = URI.parse(QIITA_HOST)
    http_client = Net::HTTP.new(uri.host, uri.port)
    http_client.use_ssl = true
    http_client
  end
end

たとえば、不正なトークンをセットしてリクエストを送った場合、以下のような例外が発生します。

$ curl 'http://localhost:3000/qiita_items'

RuntimeError (code= 401, body = {"message":"Unauthorized","type":"unauthorized"}):

カスタム例外を作成する

カスタム例外には例外の発生場所がわかりやすくなるというメリットがあります。カスタム例外の作成については賛否両論ありますが一応紹介しておきます。

今回はStandardErrorを継承したHTTPErrorというカスタム例外を作成しました。

lib/qiita_api_client.rb
class QiitaApiClient
  class HTTPError < StandardError
    def initialize(response)
      super "code=#{response.code} body=#{response.body}"
    end
  end

  def initialize
    @token = Rails.application.credentials.qiita[:token]
  end

  def get_items
    request = Net::HTTP::Get.new(
      '/api/v2/items',
      'Authorization' => "Bearer #{@token}"
    )
    http_client.request(request)
    response = http_client.request(request)
    case response
    when Net::HTTPSuccess
      JSON.parse(response.body)
    else
      raise QiitaApiClient::HTTPError.new(response)
    end
  end

  class << self
    def client
      QiitaApiClient.new
    end

    def get_items
      client.get_items
    end
  end

  private

  QIITA_HOST = 'https://qiita.com'

  def http_client
    uri = URI.parse(QIITA_HOST)
    http_client = Net::HTTP.new(uri.host, uri.port)
    http_client.use_ssl = true
    http_client
  end
end

これにより、カスタム例外のクラスで例外処理がされるようになりました。

$ curl 'http://localhost:3000/qiita_items'

QiitaApiClient::HTTPError (code=401 body={"message":"Unauthorized","type":"unauthorized"}):

まとめ

以上でnet/httpを利用した外部API連携の方法の紹介を終わります。

外部APIと連携する専用のクラスを作成することでQiitaApiClient.get_itemsのような形で外部サービスのデータを取得できます。
今回はシンプルなGETメソッドのみを実装しましたが、同様の手順でクエリやリクエストボディのついたメソッドの実装もできます。

今回のサンプルはあくまで一例ですので、よりよい方法があれば教えていただけるとありがたいです。

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!

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

【Rails】Travis CIを導入してdb:createで詰まったこと

はじめに

自動テストツールTravis CIを導入するときにつまづいたことを書きます。

環境

・Ruby 2.6.5
・Rails 6.0.3.2
・Mysql Ver 14.14 Distrib 5.6.47

問題点

以下のような.travis.ymlに記述しているdb:createがどうしても通らないという問題が発生しました。

script:
  - bundle exec rake db:create RAILS_ENV=test
  - bundle exec rake db:migrate RAILS_ENV=test
  - bundle exec rspec

Travis CIの画面ではこのようなエラーメッセージがでました。

DBcreate.png

Mysql2::Error::ConnectionError: Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)

いろいろ調べてみるとソケットファイルが無いということがわかりました。
database.ymlの記述のソケットファイルへのパスが誤っているということです。
今回使用したDBは MySQLです。

test:
  <<: *default
  database: (myapp)_test
  adapter: mysql2
  encoding: utf8
  username: root
  password:
  socket: (ここの記述)

解決策

でもローカルではないTravis CIのなかのファイル構成はどうしてもわからなかったので自力で確認することにしました。

まずはソケットファイルの場所を調べるコマンドは以下です。

$ mysql_config --socket

ローカル環境ではこれでわかるので、もしかしたら.travis.ymlscript:に記述すれば「Travis CI上のmysqld.sockの場所がわかるかもと思い、この記述をプラスしました。(正しいやり方かはわかりませんけど・・・)

script:
  - mysql_config --socket  (←ソケットファイルを確認する)
  - bundle exec rake db:create RAILS_ENV=test
  - bundle exec rake db:migrate RAILS_ENV=test
  - bundle exec rspec

すると、

MySQL.soke.png

なんと値が返ってきました!515行目の記述です!

/var/run/mysql/mysqld.sock

これをdatabase.ymlに記述すれば、無事にdb:createが通りました!

ローカルの時とTravisCIの時の使い分け

このままだと逆にローカルでのテストがうまくいかないのでTravisCIでのテストの時には上記でしらべたソケットを参照するようにします。

TravisCI専用としてdatabase_travis.ymlを作成し以下のような記述。

test:
  <<: *default
  database: (myapp)_test
  adapter: mysql2
  encoding: utf8
  username: root
  password:
  socket: /var/run/mysqld/mysqld.sock

そして.travis.ymlに以下を追記

before_script:
  - "cp config/database_travis.yml config/database.yml"

この時だけ書き換えるようにしたらローカルでもTravisCIでもテストが動きました。
データベースのエラーはなかなか原因にたどり着けなく苦労しました。

buildpass.png

無事にこのバッジをReadmeにつけることができました!!

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

【Ruby/Rails】Rails6でのjQuery導入方法

はじめに

やりたいこと

  • いいね機能のAjax対応を実装したい
  • そのためにjQueryを導入したい

動作環境

ruby 2.6.5 / Rails 6.0.3.4

やってみて

Rails6から標準装備されているWebpackerを利用することで簡単に実装できました。
多くの記事がRails5までの情報でとっっっても遠回りしたので、6以降の方は僕のように無駄な時間を使わないでいただけたら…

参考URL

Railsガイド > Rails で JavaScript を使用する

手順

  • jQueryのインストール
  • Webpackの設定
  • application.jsの設定
  • 動作確認

 jQueryのインストール

そもそもjQueryとは

JavaScriptをより簡単に記述するためのライブラリのひとつ。

したがって、jQuery(ライブラリ)を使うのであればJavaScript(元のプログラミング言語)にコンパイル(翻訳)する必要があります。
コンパイルする方法はいろいろありますが、初学者故、とりあえず簡単なWebpackerを使用していこう!というのが本記事の主旨であります。

ではまず、jQueryのインストールを行います。

ターミナル
% yarn add jquery

Rails5以前の導入方法ではjquery-railsというGemをインストールするのが基本線のようですが、Webpackerで管理する際はyarnコマンドを使用してインストールします。

yarnとは

JavaScriptのパッケージマネージャー。Node.jsで動作するパッケージを管理する。

Node.jsとは

本来フロントサイド開発用の言語であるJavaScriptをサーバーサイドで使うための「環境」のこと
Node.jsのおかげで簡単にAjax対応ができたりする。

つまり、Webpackerという翻訳機にjQueryという言語をyarnというサーバー用の説明書で登録した、という感じでしょうか(違ってたらごめんなさい)

Webpackの設定

Webpackの設定ファイルでjQueryを管理下として認定します。

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の設定

application.jsでjQueryを呼び出せるようにします。

javascript/packs/application.js
//中略

require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
// 追記
require('jquery')

//中略

以上で導入は完了です。

動作確認

jQueryが問題なく動作しているか確認します。
好きなページに次のコードを転記します。

好きなページ.html.erb
<p>テスト</p>
<script type="text/javascript">
  $(document).ready(function() {
    $("p").text("成功!!");
  });
</script>

p要素のテキストに”成功!!”を代入するようになっています。

(成功例)
スクリーンショット 2020-11-08 18.55.05.png
このように「テスト」ではなく「成功!!」と表示されていれば動作確認は完了です。

おわりに

いろいろな記事を調べてgemを導入したりしていたのですが、Webpackerを使えば簡単に実装できました。

特に今回、JavaScriptとjQueryを調べていく中で、Node.jsのことを表面的にでも理解できたのはよかったかなと思います。

参考にさせていただいた記事
エンジニアの入り口 > 初心者向け!3分で理解するNode.jsとは何か?


Ruby / Rails 初学者向けの記事を書いています。
今後も週3〜4記事ペースで更新していきたいと思いますので、初学者のみなさん、ぜひフォローお願いします!!

最後までお読みいただきありがとうございました!

✔︎

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

【ActiveAdmin】デフォルトのcreateとupdateの処理をカスタマイズしたい

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

【ActiveAdmin】select_boxで表示するcollectionのscopeを指定したい

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

Rails, RSpecの導入手順

環境

rails 6.0.3.4
ruby 2.6.5

流れ

・モデルの単体テストの目的、何をテストするのか
・RSpec導入準備(必要なgemのインストール)
このような流れで説明していきます。

1 テストの目的

結論、コードの保守性を保つためです。当たり前ですがリリースしたアプリケーションにバグが生じるのは望ましくありません。バグが生じないことを手作業で確かめるの思わぬ見落としがあるかもしれない、ということで信頼できるテストを書いておけばコードに変更があった際でも「テストが通ったからok」と手作業で挙動を確かめる手間を省けるということです。

2 何をテストするか

① バリデーション
データベースに値を保存する際には「空の値を保存しない」、「文字数は何文字以内」、「数字のみ保存する」、「大文字は小文字に変換する」などのルールを定義します。規則性のない値が保存されてしまうことがデータベース設計上望ましくないからです。rails側でこのルールを定義する方法がモデルのバリデーションです。そのため、モデルのテストではバリデーションが正しく動作しているかをチェックします。

② メソッド
モデルにはそのモデルの振る舞いを表すメソッドを自分で定義することができます。これも手動でテストするのは大変なのでテストに組み込みます。

③ その他
モデルの役割として「アソシエーションの定義」があります。アソシエーション自体はrails側で定義されているものなので特段テストしなくていいそうですが、あるモデルのデータを削除した時に関連するモデルが削除されているかということもテストに含めます。

3 導入編

まず、RSpecの設定からです。以下のコマンドを実行します。

% bin/rails generate rspec:install

するとジェネレータが以下のようにrspecの設定ファイルと保存フォルダを生成してくれます。

Running via Spring preloader in process 28211
create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb

.rspec ファイルを開き、以下のように変更してください。これによりテストの実行結果の表示をきれいに出力できます。

.rspec
--require spec_helper
--format documentation

次にgemのインストールです。

Gemfile
group :development, :test do
  gem 'factory_bot_rails'
  gem 'rspec-rails', '~> 4.0.0'
  # 以下省略
end

group :development do
  gem 'spring-commands-rspec'
  # 以下省略
end

最後の'spring-commands-rspec'はRSpecテストランナーのためのbinstubです。これによりアプリケーションの起動時間を早くするspringの恩恵を受けられます。Springを使いたくない場合は無視してください。最後にもう一つ設定する項目があります。rails gコマンドを実行した際にRSec用のスペックファイルも一緒に作ってもらうようRailsを設定しましょう。また、不要なファイルを生成しないようにします。
config/application.rbを開き、以下のように編集します。

config/application.rb
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.

    config.generators do |g|
      g.test_framework :rspec,
                       view_specs: false,
                       helper_specs: false,
                       routing_specs: false
    end
 end

これでRSpecを導入するための設定が完了しました!

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

備忘録:Railsによるコメント機能実装

はじめに

Rubu on Railsでオリジナルアプリを作成しています。そのアプリにコメント機能を実装しました。そのため忘れないように書き記します。

手順

・commentモデル、テーブルの作成
・commentsコントローラーとルーティングの設定
・各コントローラーのアクション設定

commentモデル、テーブルの作成

はじめに下記のようにターミナルでcommentモデルを作成します。

rails g model comment

その後マイグレーションファイルに、textカラムを追加します(このtextカラムにはコメントが保存されます)。

2020*********_create_comments.rb
class CreateComments < ActiveRecord::Migration[6.0]
  def change
    create_table :comments do |t|
      t.text :text, null: false
      t.references :user, foreign_key: true
      t.references :desk, foreign_key: true
      t.timestamps
    end
  end
end

userとdeskを外部キー設定します。このコメントは誰が、どのdesk(画像)にしたものか管理するためです。同様にcommentモデルにもバリデーションの他、アソシエーションを設定する必要があります。

desk.rb
  validates :text, presence: true
  belongs_to :user
  belongs_to :desk

userモデル、deskモデルにもアソシエーションを追加します。なお、今回のバリデーションはコメントが空だと保存できないような設定です。

commentsコントローラーとルーティングの設定

ターミナルでコメントのコントローラーを作成します。この時、コントローラー名は複数形にする(いつもどっちだっけ?って悩んでます?)。

rails g controller comments

お次は作成したcommentsコントローラーにcreateアクションを設定します。このとき、中身は空で一旦OKです。

comments_controller.rb
def create
end

最後にルーティングを。コメントはdesk(画像)と紐付けします。なのでネスト(入れ子構造)にします。

route.rb
resources :desks do
    resources :comments, only: :create
end 

くどくなりますが、どの画像に対するコメントなのかをパスから判断できるようにすることが重要です。

各コントローラーのアクション設定

今回はdesks(画像)コントローラーのshowアクションに対するレスポンスページでコメント保存、表示します(元々このページは、投稿された画像の詳細ページです)。なので、まずはdeskコントローラーのshowアクションに@commentのインスタンス変数を定義します。

desks.controller.rb
def show
    @desk = Desk.find(params[:id])
    @comment = Comment.new
    @comments = @desk.comments
end

なお画像に付けられたコメントを一覧で表示するため。@commentsでコメントを取得しています。

今度はcommentsコントローラーへの設定です。こちらではストロングパラメータを設定します。requireにモデル名、permitに保存カラムを設定します。アソシエーション関係にあるものはmergeに記述します。なおdevise Gemがインストールされているので、current_userメソッドを使用しています。これによりコメントが誰が(user_id)、何に(desk_id)にされたものか管理できます。

comments.controller.rb
def create
    @comment = Comment.new(comment_params)
    if @comment.save
      redirect_to desk_path(@comment.desk)
    else
      @desk = @comment.desk
      @comments = @desk.comments
      render "desks/show"
    end
end
  private

  def comment_params
    params.require(:comment).permit(:text).merge(user_id: current_user.id, desk_id: params[:desk_id])
  end

以上

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

stripeを使いショッピングサイトを作ろう!(購入編)

プチ宣伝

https://www.code-sell.net/
コードを販売できるサービスを作りました!いらないコードがある方はぜひ使ってく見てください。ちなみにこの記事で説明するstripeを使用しています。

初めに

こんにちは!今回はrailsとstripeを使い簡単?にECサイトを作っていきます。少し前自分もrailsとstripeで コードを販売するサイトを作ったのですがstripeの情報が少なすぎて非常に苦労しました。正確には情報自体はたくさんあるのですが実践的な情報が少なくただただ公式ドキュメントのようにコードを並べちょこっと説明するみたいなものばっかりでした。今回は実際にサイトを作っていきます。

使う技術・作るもの

noteみたいな記事を購入できるもの

rails
ruby
stripe

stripeってそもそもなに?

決済システムです。payjpとくらべ手数料が安かったり送金機能があったりします。

全体像

https___stripe.com_img_docs_connect_overview.png

Customer(顧客): 購入者でありお金を支払う方。上の図で緑。
Platform(プラットフォーム): 今から作るECサイト。サービスを提供するところ。
Connected accounts: プラットフォームを利用してサービスを提供し、入金を受ける方・販売者(子アカウントとも呼ばれます)。上の図のピンク。

アカウントのタイプ

stripeにはStandardとCustomという子アカウント(販売者)のタイプがあります。特徴・登録方法が違うので目を通りておきましょう。

Standard

開発コスト(手間):簡単
ユーザー視点:微妙
何かあった時の責任:販売者(子アカウント)
おすすめ度:中

これは実装するのが非常に簡単なタイプです。あとで書きますが登録フォームやシステムはほとんどstripe側がやってくれます。僕たちはその登録フォームのリンクを張り付けちょこっとコピペでコントローラーに書くだけです。なにかあった時(マイナス残高など)も僕たちではなく利用しているユーザー側の責任となります。ただこの方法だとユーザーにstripeを使っていることがしっかり伝わってしまいます。登録フォームは完全にstripeが作っているしStripe の管理画面(ダッシュボード)へ、販売者がアクセスできるようになります。デザインも変更できません。

Custom

開発コスト(手間):難
ユーザー視点:いい
何かあった時の責任:プラットフォーム(開発者)
おすすめ度:高

これは実装するのが難しいタイプです。登録フォームもstripeに送信するシステムもダッシュボードも自分で作ります。何かあった時も自分の責任です。ただ登録から管理画面まですべて自分のサイトで完結します。デザインももちろん自由です。

準備編

ながなが説明してきましたがとりあえず細かいことは作って覚えましょう。
今回作るのは単発の購入のサイトです。定期支払などもできますがそれは別の記事でやっていたので...。

登録してAPIキーをもらう
新規登録
APIキーを取得する画面

gemをインストール

gemfile
gem "stripe"
gem 'dotenv-rails'

bundleを忘れずにー。
そしたら.envというファイルをアプリフォルダの直下に作り

PUBLISHABLE_KEY="pk_test_xxx"
SECRET_KEY="sk_test_xxx"
CLIENT_ID="ca_xxx"

と記述してください。
CLIENT_IDはこちらから取得

config/initializers/stripe.rb
Rails.configuration.stripe = {
  publishable_key: ENV["PUBLISHABLE_KEY"],
  secret_key: ENV["SECRET_KEY"],
}
Stripe.api_key = Rails.configuration.stripe[:secret_key]

アプリケーションを作っていく

最初から作るの手間なのでscaffoldにします。最初にも書きましたが今回はnoteのような記事を購入できるサービスを作ります。本当は画像アップロード機能とかもあるほうがいいですが今回はあくまでstripeが中心なのでアプリの機能は最小限にします。

rails g scaffold post title:string content:text price:integer

content...内容、商品
price...値段

rails db:migrate

これでscaffoldができたと思います。

購入機能

購入機能は意外に簡単です。とりあえず最初に作ってしまいましょう。

routes.rb
post "posts/:id/charge", to: "charge#create", as: "charge"

views

↓erbバージョン

show.html.erb
<%= form_tag charge_path(@post) do %>
  <script src="https://checkout.stripe.com/checkout.js" class="stripe-button"
    data-key="#{ENV["PUBLISHABLE_KEY"]}"
    data-amount="<%= @post.price %>"
    data-currency="jpy"
    data-description="クレジット決済"
    data-name=<%= "#{@post.title}を購入" %>
    data-email=<%= "#{current_user.email}" %>
    data-label="購入する"
    data-image="https://stripe.com/img/documentation/checkout/marketplace.png"
    data-locale="auto"
    data-allow-remember-me="false">
  </script>
<% end %>

↓slimバージョン

show.html.slim
= form_tag charge_path(@post) do
  script.stripe-button data-amount="#{@post.price}\
  " data-currency="jpy" data-description="クレジット決済\
  " data-key="#{ENV["PUBLISHABLE_KEY"]}" data-locale="auto" data-name="#{@post.title}を購入\
  " data-email="#{current_user.email}" data-label="購入する\
  " data-allow-remember-me="false" src="https://checkout.stripe.com/checkout.js"

viewsをかいたら
charges_controller.rb
というコントローラーを作ってください。

charges_controller.rb
class ChargesController < ApplicationController
  def create
    @post = Post.find(params[:id])
    customer = Stripe::Customer.create({
      email: params[:stripeEmail],
      source: params[:stripeToken],
    })
    charge = Stripe::Charge.create({
      customer: customer.id,
      amount: @post.price,
      description: "商品ID:#{@post.id} 商品名:#{@post.title}",
      currency: "jpy",
    })
  rescue Stripe::CardError => e
    flash[:error] = e.message
    redirect_to new_charge_path
  end
end

簡単に説明すると4行目でcustomer(顧客)を作っています。chargeは支払い情報をつくっています。
customerはそのまま
amountは商品の値段を設定
descriptionで商品の情報を設定(内容はなんでもいい)
currencyで扱う通貨を設定(USDやJPYなど)

これで購入ができると思います。
テストするときのカード番号は
4242 4242 4242 4242
です。
cvcはなんでもいいです。
カードの期限は今後であればいつでもいいです。
ほかにもいくつかあります。
テストカード一覧

終わりに

今回はここまでにします。
次回はdeviseを導入してマイページを作りスタンダードアカウントやカスタムアカウントの作り方を説明していこうと思います。
大変なので結構先になるかもしれません。

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

第1回 railsでショッピングサイトを作ろう!(購入編)

プチ宣伝

https://www.code-sell.net/
コードを販売できるサービスを作りました!いらないコードがある方はぜひ使ってく見てください。ちなみにこの記事で説明するstripeを使用しています。

初めに

こんにちは!今回はrailsとstripeを使い簡単?にECサイトを作っていきます。少し前自分もrailsとstripeで コードを販売するサイトを作ったのですがstripeの情報が少なすぎて非常に苦労しました。正確には情報自体はたくさんあるのですが実践的な情報が少なくただただ公式ドキュメントのようにコードを並べちょこっと説明するみたいなものばっかりでした。今回は実際にサイトを作っていきます。

使う技術・作るもの

noteみたいな記事を購入できるもの

rails
ruby
stripe

stripeってそもそもなに?

決済システムです。payjpとくらべ手数料が安かったり送金機能があったりします。

全体像

https___stripe.com_img_docs_connect_overview.png

Customer(顧客): 購入者でありお金を支払う方。上の図で緑。
Platform(プラットフォーム): 今から作るECサイト。サービスを提供するところ。
Connected accounts: プラットフォームを利用してサービスを提供し、入金を受ける方・販売者(子アカウントとも呼ばれます)。上の図のピンク。

アカウントのタイプ

stripeにはStandardとCustomという子アカウント(販売者)のタイプがあります。特徴・登録方法が違うので目を通りておきましょう。

Standard

開発コスト(手間):簡単
ユーザー視点:微妙
何かあった時の責任:販売者(子アカウント)
おすすめ度:中

これは実装するのが非常に簡単なタイプです。あとで書きますが登録フォームやシステムはほとんどstripe側がやってくれます。僕たちはその登録フォームのリンクを張り付けちょこっとコピペでコントローラーに書くだけです。なにかあった時(マイナス残高など)も僕たちではなく利用しているユーザー側の責任となります。ただこの方法だとユーザーにstripeを使っていることがしっかり伝わってしまいます。登録フォームは完全にstripeが作っているしStripe の管理画面(ダッシュボード)へ、販売者がアクセスできるようになります。デザインも変更できません。

Custom

開発コスト(手間):難
ユーザー視点:いい
何かあった時の責任:プラットフォーム(開発者)
おすすめ度:高

これは実装するのが難しいタイプです。登録フォームもstripeに送信するシステムもダッシュボードも自分で作ります。何かあった時も自分の責任です。ただ登録から管理画面まですべて自分のサイトで完結します。デザインももちろん自由です。

準備編

ながなが説明してきましたがとりあえず細かいことは作って覚えましょう。
今回作るのは単発の購入のサイトです。定期支払などもできますがそれは別の記事でやっていたので...。

登録してAPIキーをもらう
新規登録
APIキーを取得する画面

gemをインストール

gemfile
gem "stripe"
gem 'dotenv-rails'

bundleを忘れずにー。
そしたら.envというファイルをアプリフォルダの直下に作り

PUBLISHABLE_KEY="pk_test_xxx"
SECRET_KEY="sk_test_xxx"
CLIENT_ID="ca_xxx"

と記述してください。
CLIENT_IDはこちらから取得

config/initializers/stripe.rb
Rails.configuration.stripe = {
  publishable_key: ENV["PUBLISHABLE_KEY"],
  secret_key: ENV["SECRET_KEY"],
}
Stripe.api_key = Rails.configuration.stripe[:secret_key]

アプリケーションを作っていく

最初から作るの手間なのでscaffoldにします。最初にも書きましたが今回はnoteのような記事を購入できるサービスを作ります。本当は画像アップロード機能とかもあるほうがいいですが今回はあくまでstripeが中心なのでアプリの機能は最小限にします。

rails g scaffold post title:string content:text price:integer

content...内容、商品
price...値段

rails db:migrate

これでscaffoldができたと思います。

購入機能

購入機能は意外に簡単です。とりあえず最初に作ってしまいましょう。

routes.rb
post "posts/:id/charge", to: "charge#create", as: "charge"

views

↓erbバージョン

show.html.erb
<%= form_tag charge_path(@post) do %>
  <script src="https://checkout.stripe.com/checkout.js" class="stripe-button"
    data-key="#{ENV["PUBLISHABLE_KEY"]}"
    data-amount="<%= @post.price %>"
    data-currency="jpy"
    data-description="クレジット決済"
    data-name=<%= "#{@post.title}を購入" %>
    data-email=<%= "#{current_user.email}" %>
    data-label="購入する"
    data-image="https://stripe.com/img/documentation/checkout/marketplace.png"
    data-locale="auto"
    data-allow-remember-me="false">
  </script>
<% end %>

↓slimバージョン

show.html.slim
= form_tag charge_path(@post) do
  script.stripe-button data-amount="#{@post.price}\
  " data-currency="jpy" data-description="クレジット決済\
  " data-key="#{ENV["PUBLISHABLE_KEY"]}" data-locale="auto" data-name="#{@post.title}を購入\
  " data-email="#{current_user.email}" data-label="購入する\
  " data-allow-remember-me="false" src="https://checkout.stripe.com/checkout.js"

viewsをかいたら
charges_controller.rb
というコントローラーを作ってください。

charges_controller.rb
class ChargesController < ApplicationController
  def create
    @post = Post.find(params[:id])
    customer = Stripe::Customer.create({
      email: params[:stripeEmail],
      source: params[:stripeToken],
    })
    charge = Stripe::Charge.create({
      customer: customer.id,
      amount: @post.price,
      description: "商品ID:#{@post.id} 商品名:#{@post.title}",
      currency: "jpy",
    })
  rescue Stripe::CardError => e
    flash[:error] = e.message
    redirect_to new_charge_path
  end
end

簡単に説明すると4行目でcustomer(顧客)を作っています。chargeは支払い情報をつくっています。
customerはそのまま
amountは商品の値段を設定
descriptionで商品の情報を設定(内容はなんでもいい)
currencyで扱う通貨を設定(USDやJPYなど)

これで購入ができると思います。
テストするときのカード番号は
4242 4242 4242 4242
です。
cvcはなんでもいいです。
カードの期限は今後であればいつでもいいです。
ほかにもいくつかあります。
テストカード一覧

終わりに

今回はここまでにします。
次回はdeviseを導入してマイページを作りスタンダードアカウントやカスタムアカウントの作り方を説明していこうと思います。
大変なので結構先になるかもしれません。

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

railsでショッピングサイトを作ろう!(購入編)

プチ宣伝

https://www.code-sell.net/
コードを販売できるサービスを作りました!いらないコードがある方はぜひ使ってく見てください。ちなみにこの記事で説明するstripeを使用しています。

初めに

こんにちは!今回はrailsとstripeを使い簡単?にECサイトを作っていきます。少し前自分もrailsとstripeで コードを販売するサイトを作ったのですがstripeの情報が少なすぎて非常に苦労しました。正確には情報自体はたくさんあるのですが実践的な情報が少なくただただ公式ドキュメントのようにコードを並べちょこっと説明するみたいなものばっかりでした。今回は実際にサイトを作っていきます。

使う技術・作るもの

noteみたいな記事を購入できるもの

rails
ruby
stripe

stripeってそもそもなに?

決済システムです。payjpとくらべ手数料が安かったり送金機能があったりします。

全体像

https___stripe.com_img_docs_connect_overview.png

Customer(顧客): 購入者でありお金を支払う方。上の図で緑。
Platform(プラットフォーム): 今から作るECサイト。サービスを提供するところ。
Connected accounts: プラットフォームを利用してサービスを提供し、入金を受ける方・販売者(子アカウントとも呼ばれます)。上の図のピンク。

アカウントのタイプ

stripeにはStandardとCustomという子アカウント(販売者)のタイプがあります。特徴・登録方法が違うので目を通りておきましょう。

Standard

開発コスト(手間):簡単
ユーザー視点:微妙
何かあった時の責任:販売者(子アカウント)
おすすめ度:中

これは実装するのが非常に簡単なタイプです。あとで書きますが登録フォームやシステムはほとんどstripe側がやってくれます。僕たちはその登録フォームのリンクを張り付けちょこっとコピペでコントローラーに書くだけです。なにかあった時(マイナス残高など)も僕たちではなく利用しているユーザー側の責任となります。ただこの方法だとユーザーにstripeを使っていることがしっかり伝わってしまいます。登録フォームは完全にstripeが作っているしStripe の管理画面(ダッシュボード)へ、販売者がアクセスできるようになります。デザインも変更できません。

Custom

開発コスト(手間):難
ユーザー視点:いい
何かあった時の責任:プラットフォーム(開発者)
おすすめ度:高

これは実装するのが難しいタイプです。登録フォームもstripeに送信するシステムもダッシュボードも自分で作ります。何かあった時も自分の責任です。ただ登録から管理画面まですべて自分のサイトで完結します。デザインももちろん自由です。

準備編

ながなが説明してきましたがとりあえず細かいことは作って覚えましょう。
今回作るのは単発の購入のサイトです。定期支払などもできますがそれは別の記事でやっていたので...。

登録してAPIキーをもらう
新規登録
APIキーを取得する画面

gemをインストール

gemfile
gem "stripe"
gem 'dotenv-rails'

bundleを忘れずにー。
そしたら.envというファイルをアプリフォルダの直下に作り

PUBLISHABLE_KEY="pk_test_xxx"
SECRET_KEY="sk_test_xxx"
CLIENT_ID="ca_xxx"

と記述してください。
CLIENT_IDはこちらから取得

config/initializers/stripe.rb
Rails.configuration.stripe = {
  publishable_key: ENV["PUBLISHABLE_KEY"],
  secret_key: ENV["SECRET_KEY"],
}
Stripe.api_key = Rails.configuration.stripe[:secret_key]

アプリケーションを作っていく

最初から作るの手間なのでscaffoldにします。最初にも書きましたが今回はnoteのような記事を購入できるサービスを作ります。本当は画像アップロード機能とかもあるほうがいいですが今回はあくまでstripeが中心なのでアプリの機能は最小限にします。

rails g scaffold post title:string content:text price:integer

content...内容、商品
price...値段

rails db:migrate

これでscaffoldができたと思います。

購入機能

購入機能は意外に簡単です。とりあえず最初に作ってしまいましょう。

routes.rb
post "posts/:id/charge", to: "charge#create", as: "charge"

views

↓erbバージョン

show.html.erb
<%= form_tag charge_path(@post) do %>
  <script src="https://checkout.stripe.com/checkout.js" class="stripe-button"
    data-key="#{ENV["PUBLISHABLE_KEY"]}"
    data-amount="<%= @post.price %>"
    data-currency="jpy"
    data-description="クレジット決済"
    data-name=<%= "#{@post.title}を購入" %>
    data-email=<%= "#{current_user.email}" %>
    data-label="購入する"
    data-image="https://stripe.com/img/documentation/checkout/marketplace.png"
    data-locale="auto"
    data-allow-remember-me="false">
  </script>
<% end %>

↓slimバージョン

show.html.slim
= form_tag charge_path(@post) do
  script.stripe-button data-amount="#{@post.price}\
  " data-currency="jpy" data-description="クレジット決済\
  " data-key="#{ENV["PUBLISHABLE_KEY"]}" data-locale="auto" data-name="#{@post.title}を購入\
  " data-email="#{current_user.email}" data-label="購入する\
  " data-allow-remember-me="false" src="https://checkout.stripe.com/checkout.js"

viewsをかいたら
charges_controller.rb
というコントローラーを作ってください。

charges_controller.rb
class ChargesController < ApplicationController
  def create
    @post = Post.find(params[:id])
    customer = Stripe::Customer.create({
      email: params[:stripeEmail],
      source: params[:stripeToken],
    })
    charge = Stripe::Charge.create({
      customer: customer.id,
      amount: @post.price,
      description: "商品ID:#{@post.id} 商品名:#{@post.title}",
      currency: "jpy",
    })
  rescue Stripe::CardError => e
    flash[:error] = e.message
    redirect_to new_charge_path
  end
end

簡単に説明すると4行目でcustomer(顧客)を作っています。chargeは支払い情報をつくっています。
customerはそのまま
amountは商品の値段を設定
descriptionで商品の情報を設定(内容はなんでもいい)
currencyで扱う通貨を設定(USDやJPYなど)

これで購入ができると思います。
テストするときのカード番号は
4242 4242 4242 4242
です。
cvcはなんでもいいです。
カードの期限は今後であればいつでもいいです。
ほかにもいくつかあります。
テストカード一覧

終わりに

今回はここまでにします。
次回はdeviseを導入してマイページを作りスタンダードアカウントやカスタムアカウントの作り方を説明していこうと思います。
大変なので結構先になるかもしれません。

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

Railsで RoutingError がFATALレベルでログに出てくることへ対処(したかった)

概要

RoutingErrorFATALレベルでログ出力される点が困っている。FATALは運用時に「即時対処が必要なもの」と決めていて夜間でもアラートが鳴るようにしているのだが、 RoutingError はクライアントが不正なリクエストをすれば簡単に起こせるので、そもそもログに出されなくていい。(404はwebサーバーのアクセスログでもわかる)

何か設定を間違えているだけかと思っていたが、Railsガイド通りに新規作成したばかりのrailsアプリでも同じことが起きたため、railsが何をしているのかソースコードを色々と調べることになった。結局適切なオプションは見つけられず、railsの MiddlewareStack に手を加えるという形になってしまった。(この辺の仕組みに目を通せたのは収穫。)

環境: Rails 6.0.0 / Ruby 2.5.3 / Ubuntu 20.04

ログのサンプル

リクエストを受けた際のrailsのログを以下に示す。

ルーティングあり
I, [2020-11-07T23:01:42.988933 #12481]  INFO -- : Started GET "/" for 127.0.0.1 at 2020-11-07 23:01:42 +0900
I, [2020-11-07T23:01:42.992722 #12481]  INFO -- : Processing by MiniController#index as HTML
I, [2020-11-07T23:01:43.002614 #12481]  INFO -- :   Rendering text template
I, [2020-11-07T23:01:43.005314 #12481]  INFO -- :   Rendered text template (Duration: 0.0ms | Allocations: 4)
I, [2020-11-07T23:01:43.005728 #12481]  INFO -- : Completed 200 OK in 9ms (Views: 8.9ms | Allocations: 1395)
ルーティングなし
I, [2020-11-07T23:03:08.801059 #12481]  INFO -- : Started GET "/illegal" for 127.0.0.1 at 2020-11-07 23:03:08 +0900
F, [2020-11-07T23:03:08.801822 #12481] FATAL -- :   
ActionController::RoutingError (No route matches [GET] "/illegal"):

actionpack (6.0.0) lib/action_dispatch/middleware/debug_exceptions.rb:36:in `call'
actionpack (6.0.0) lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
(省略)
/opt/rbenv/versions/2.5.3/lib/ruby/2.5.0/webrick/server.rb:307:in `block in start_thread'

ちなみにrails5ではFATALログが4つに分割されていた(空行・例外・空行・バックトレース)。rails6ではこの通りひとまとめになったので、監視側でログを無視するのは楽になった。

ログが出るまでの仕組み

そもそも RoutingError はどう発生し、どこでログに記録しているのだろうと疑問に思った。

ミドルウェアの入れ子

ログのスタックトレースを頼りにgemをgrepしていたところ、 Rails::Application::DefaultMiddlewareStack の中で一連のアプリを登録していた。 ※railsの設定によって個数は変わる

ミドルウェア
::ActionDispatch::HostAuthorization
::ActionDispatch::SSL
::Rack::Sendfile
::ActionDispatch::Static
::Rack::Cache
::Rack::Lock
::ActionDispatch::Executor
::Rack::Runtime
::Rack::MethodOverride
::ActionDispatch::RequestId
::ActionDispatch::RemoteIp
::Rails::Rack::Logger
::ActionDispatch::ShowExceptions
::ActionDispatch::DebugExceptions
::ActionDispatch::ActionableExceptions
::ActionDispatch::Reloader
::ActionDispatch::Callbacks
::ActionDispatch::Cookies
config.session_store
::ActionDispatch::Flash
::ActionDispatch::ContentSecurityPolicy::Middleware
::Rack::Head
::Rack::ConditionalGet
::Rack::ETag
::Rack::TempfileReaper

これらのクラスには #call メソッドがあり、上側のクラスはひとつ下のクラスにリクエストを渡してレスポンスを受け取るという入れ子構造になっている。もちろんただ #call を呼ぶだけでなく、前後にリクエスト・レスポンスを編集したり、例外の送出や捕捉なども必要に応じてしていて、それぞれが自分の役割を果たしている。

※入れ子の順序は MiddlewareStack が管理するので、各クラスは自分が誰を呼び出すか知っている必要は無い。

DebugExceptions

肝心のこのアプリが何をしているのか、中身を読んだ。

https://github.com/rails/rails/blob/v6.0.0/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L30

ActionDispatch::DebugExceptions#call
    def call(env)
      request = ActionDispatch::Request.new env
      _, headers, body = response = @app.call(env)

      if headers["X-Cascade"] == "pass"
        body.close if body.respond_to?(:close)
        raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
      end

      response
    rescue Exception => exception
      invoke_interceptors(request, exception)
      raise exception unless request.show_exceptions?
      render_exception(request, exception)
    end
  1. すぐ次のアプリにリクエストを渡してレスポンスを受け取る
  2. レスポンスヘッダーに X-Cascade: pass が設定されていたら RoutingError を送出する
  3. 処理中に例外が起きていれば捕捉し、必要に応じて処理をする
    • interceptorが登録されていれば実行する(rails6で追加)
    • 例外をログに記録する
    • 例外の詳細についてのレスポンスを作成する ※development環境でよく出てくるやつ
    • レスポンスを作成しないなら例外を再送出する(前段のアプリに任せる)

この「例外をログに記録する」処理を回避でき、他の処理にも悪影響が出ない条件分岐があればいい。(なお、ログ出力は fatal でハードコードされていて、レベルを下げることはできない)

対処(の試行錯誤)

案1. DebugExceptions を外す

「クラス名がdebugだし、無くても動くだろう」と思って試した。railsの設定時に、登録したミドルウェアを抜くことができる。

config/environments/<env>.rb などに追加
config.middleware.delete ::ActionDispatch::DebugExceptions

するとFATALログは消えたものの、レスポンスボディが Not Found だけになってしまい、404ページが表示されなくなった

クライアント側
$ curl -i http://localhost:3000/illegal
HTTP/1.1 404 Not Found 
X-Cascade: pass
Cache-Control: no-cache
X-Request-Id: fefa1568-57dd-45be-bc31-f91f17a5c916
X-Runtime: 0.005418
Server: WEBrick/1.4.2 (Ruby/2.5.3/2018-10-18)
Date: Sat, 07 Nov 2020 14:32:30 GMT
Content-Length: 9
Connection: Keep-Alive

Not Found

DebugExceptions を外すということは RoutingError が出なくなるということであり、前段のアプリ群が元と同じ例外処理をしなくなってしまう名前に反してかなり重要なミドルウェア?

というわけで却下。

案2. show_exceptions オプション

DebugExceptions の中でログ出力せずに例外を投げてくれればいいだろう」ということで、コード中の request.show_exceptions? が偽になるようにする。これはrailsの設定項目に存在する。

config/environments/<env>.rb などに追加
config.action_dispatch.show_exceptions = false

やってみたら500エラーになってしまった。ログもrailsは問題ないが、webサーバー(webrick, pumaなど)のほうでエラーを吐き出している。

illegal-500.png

ログ
I, [2020-11-07T23:34:09.728873 #13864]  INFO -- : Started GET "/illegal" for 127.0.0.1 at 2020-11-07 23:34:09 +0900
[2020-11-07 23:34:09] ERROR ActionController::RoutingError: No route matches [GET] "/illegal"
    /.../actionpack-6.0.0/lib/action_dispatch/middleware/debug_exceptions.rb:36:in `call'
    /.../actionpack-6.0.0/lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
(省略)
    /opt/rbenv/versions/2.5.3/lib/ruby/2.5.0/webrick/server.rb:307:in `block in start_thread'

というのも、これは名前通り前段の ShowExceptions を無効化するもので、 /public/404.html などエラーレスポンスが用意されなくなる。さらに、元々はここで RoutingError を捕捉していたのに再送出してしまうようになり、前段のアプリ群の処理が変わってしまう。

  • ActionDispatch::ShowExceptions rescues any exception returned by the application and renders nice exception pages if the request is local or if config.consider_all_requests_local is set to true. If config.action_dispatch.show_exceptions is set to false, exceptions will be raised regardless.

https://guides.rubyonrails.org/configuring.html#configuring-middleware より引用、一部強調

というわけで却下。

案3. DebugExceptions にモンキーパッチ

確実に成功する方法を試しておく。問題のFATALログの出力箇所はひとつのメソッドに纏められているので、それを何もしないように上書きしてしまえばいい。

config/environments/<env>.rb などに追加
class ::ActionDispatch::DebugExceptions
  def log_error(*); end
end

成功例は省略する。問題としては、

  • RoutingError 以外の例外もログ出力されなくなってしまう
  • ライブラリのコード変更に弱い(しかも対象がprivateメソッド)
  • モンキーパッチということで行儀が悪い

案4. DebugExceptions の代替品を自作

案3よりは正攻法であり、柔軟性があり、その分だけ面倒でもある。

my_debug_exceptions.rb
# RoutingError の送出部分だけをコピーした
class MyDebugExceptions
  # 第1引数 app は必須、追加の引数は自由(今回は無視)
  def initialize(app, *)
    @app = app
  end

  # 引数は env
  def call(env)
    request = ActionDispatch::Request.new env
    _, headers, body = response = @app.call(env)

    if headers["X-Cascade"] == "pass"
      body.close if body.respond_to?(:close)
      raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
    end

    response
  rescue ActionController::RoutingError => routing_error
    # RoutingError 専用の処理、今回はひとまず無し
    raise routing_error
  rescue Exception => exception
    # RoutingError 以外の処理、今回はひとまず無し
    raise exception
  end
end

作成したアプリを元のものと差し替える。これはrailsの設定で可能。

config/environments/<env>.rb などに追加
# app 以外の引数はここで指定できる
config.middleware.swap ActionDispatch::DebugExceptions,
                       MyDebugExceptions, "arg1", "arg2"

結局…

「本当にこんな方法しか無いの?」という気持ちになった。リリースするものに組み込むのは躊躇する。一応もう少し調べ続けてみようと思う。

先に述べた通り、rails6では問題のFATALログがひとまとめになったので、railsには標準的な動作をさせておいて監視側で無視したほうがわかりやすい気もする。

付録

実験に使用したrailsアプリ

実験ではほとんどの設定を削ぎ落とすため、過去に作成した20行程度のものをベースにした。以下にコード全文を載せる。

クリックして展開
Gemfile
source "https://rubygems.org"
gem "railties", "6.0.0"
bin/rails
APP_PATH = File.expand_path('../config/application', __dir__)
require 'rails/commands'
config.ru
require_relative 'config/environment'
run Rails.application
config/environment.rb
require_relative 'application'
Rails.application.initialize!
config/application.rb
require 'action_controller/railtie'

class MiniApp < Rails::Application
  config.logger = ::Logger.new(STDOUT)  # ログをターミナル上で見れるように
end
config/routes.rb
Rails.application.routes.draw do
  root to: 'mini#index'
end
app/controllers/mini_controller.rb
class MiniController < ActionController::Base
  def index
    render plain: "Hello, world!\n"
  end
end

エラーページの静的ファイル public/404.html はお好みで。

(コードはここまで)


development環境だと設定が追加されることがあるので、production環境で実行する。 SECRET_KEY_BASE を指定する必要があるが、適当でいい。

実行方法
$ bundle install --path vendor/bundle  # 検索やコード改変しやすいように

$ bundle exec rails routes  # ルーティング確認

$ RAILS_ENV=production SECRET_KEY_BASE=_ bundle exec rails server

あとは curl やブラウザで http://localhost:3000/ にアクセスすれば試せる。

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

【Rails】DM(チャット)機能 + Ajax 実装!

まえがき

以前スクールの課題でDM機能を実装した際に、色々なサイトを参考にして作成したのですが、かなり複雑で無駄な記述が多くなってしまいました。無駄な部分を省きながらoutputしていきたいと思い投稿しました。おかしな点はご指摘お願いします、、!

実装環境

ruby '2.6.3'
gem 'rails', '~> 5.2.4', '>= 5.2.4.3'
gem 'jquery-rails'
gem 'devise'(DM機能実装過程でヘルパーメソッドのcurrent_userを使用しています)
gem 'bootstrap-sass', '~> 3.3.6'(無くても可)

userモデルは事前に作成して、他ユーザーのshowページを閲覧できるようにしてある前提で進めていきます。

実装していく内容

usersのshowページにリンクを作成し、DMページ(chatsのshow)へ遷移して実際にトークが行えるように実装していきます。
model名やページ内に表示したい文字列等は適当に置き換えてください。

実装後のイメージ

image.png

※スタイルはかなり適当なのでご自身で整えてください

実装していきましょう!!

①モデルとカラムの作成

rails g model room
rails g model user_room user_id:integer room_id:integer
rails g model chat user_id:integer room_id:integer message:string
rails db:migrate

②コントローラーの作成

rails g controller chats show

解説(クリックしてください)
showアクションも同時に作成してしまいます。

③モデル同士のアソシエーション

user.rb
 has_many :user_rooms
 has_many :chats
 has_many :rooms, through: :user_rooms
room.rb
 has_many :chats
 has_many :user_rooms
user_room.rb
 belongs_to :user
 belongs_to :room
chat.rb
 belongs_to :user
 belongs_to :room

throughについて上手く説明できる自信がないので下記のQiita記事を参考にしてみてください☟
【初心者向け】丁寧すぎるRails『アソシエーション』チュートリアル【幾ら何でも】【完璧にわかる】?

④ルーティングの作成

routes.rb
  get 'chat/:id' => 'chats#show', as: 'chat'
  resources :chats, only: [:create]

⑤users_controllerへ記述

users_controller.rb
def show
  @user = User.find(params[:id])
end
#余計なコードは省いて記述しています。

⑥usersのshowにリンク作成

users/show
<% if current_user != @user %>
  <%= link_to 'チャットする', chat_path(@user.id) %>
<% end %> 

解説(クリックしてください)
current_userはDeviseのヘルパーメソッドです、ここではログインユーザーと@userが一致しない場合のみリンクを表示する記述しています。
pathの内容は一応rails routesで確認してから記述してください。
現時点でリンクをクリックすると作成してあるshowページに遷移するはずです。

⑦chatsコントローラーに記述

chats_controller.rb
 def show
    @user = User.find(params[:id])
    #ログインしているユーザーのidが入ったroom_idのみを配列で取得(該当するroom_idが複数でも全て取得)
    rooms = current_user.user_rooms.pluck(:room_id)
    #user_idが@user 且つ room_idが上で取得したrooms配列の中にある数値のもののみ取得(1個または0個のはずです)
    user_rooms = UserRoom.find_by(user_id: @user.id, room_id: rooms)

    if user_rooms.nil? #上記で取得できなかった場合の処理
      #新しいroomを作成して保存
      @room = Room.new
      @room.save
      #@room.idと@user.idをUserRoomのカラムに配列で保存
      UserRoom.create(user_id: @user.id, room_id: @room.id)
      #@room.idとcurrent_user.idをUserRoomのカラムに配列で保存
      UserRoom.create(user_id: current_user.id, room_id: @room.id)
    else
      #取得している場合は、user_roomsに紐づいているroomテーブルのレコードを@roomに代入
      @room = user_rooms.room
    end
    #if文の中で定義した@roomに紐づくchatsテーブルのレコードを代入
    @chats = @room.chats
    #@room.idを代入したChat.newを用意しておく(message送信時のform用)←筆者の表現が合っているか分かりません、、
    @chat = Chat.new(room_id: @room.id)
  end

  def create
    @chat = current_user.chats.new(chat_params)
    @chat.save
  end

  private
  def chat_params
    params.require(:chat).permit(:message, :room_id)
  end

⑧Chatsのshowに記述

chats/show.html.erb
<div class="container">
    <div class="row">
        <div class="col-xs-6">
            <h2 id="room" data-room="<%= @room.id %>" data-user="<%= current_user.id %>"><%= @user.name %> さんとのチャット</h2>

            <table class="message table">
              <thead>
                <tr>
                  <th style="text-align: left; font-size: 20px;"><%= current_user.name %></th>
                  <th style="text-align: right; font-size: 20px;"><%= @user.name %></th>
                </tr>
              </thead>
              <% @chats.each do |chat| %>
                <% if chat.user_id == current_user.id %>
                <tbody>
                  <tr>
                    <th>
                      <p style="text-align: left;"><%= chat.message %></p>
                    </th>
                    <th></th>
                  </tr>
                </tbody>
                <% else %>
                <tbody>
                  <tr>
                    <th></th>
                    <th>
                      <p style="text-align: right;"><%= chat.message %></p>
                    </th>
                  </tr>
                </tbody>
                <% end %>
              <% end %>
            </table>

            <%= form_with model: @chat do |f| %>
              <%= f.text_field :message %>
              <%= f.hidden_field :room_id %>
            <% end %>
        </div>
    </div>
</div>

※bootstrapで少しだけそれっぽい見た目にしているのでinstallしてない方は、必要のないdivタグやclass指定は省いて必要な部分のみの記述にしてください。

解説(クリックしてください)
form_withの記述でlocal: trueを記述していない点に注意してください。js形式のリクエストを送信する必要があるので何も記述しなくても問題ありません、<%= form_with model: @chat, remote: true do |f| %>と記述するのと同義になります。
destroyアクションなど追加して非同期にする際は、そちらにremote: trueと記述する必要があります。

⑨jsファイル作成(あと少しです!)

app/views/chats直下ににcreate.js.erbを作成して、以下の内容を記述します。

chats/create.js.erb
$('.message').append("<p style='text-align: left;'><%= @chat.message %></p>");
$('input[type=text]').val("")

解説(クリックしてください).meesageは指定してあるclass名です。
.append("

<%= @chat.message %>

");
で部分的な更新をしています。2行目の記述で更新時に入力フォームの中身を空にする処理の記述をしています。

・筆者は.appendについて上手く説明できないので以下の記事を参考にしてみてください☟
jQueryのappendメソッド

以上で実装が出来たはずです。
おかしな記述や表現がある場合は指摘をおねがいします。
良ければこちらの記事も参考にしてみてください☟
Gemなし 複数検索機能の実装 に関する筆者の記事

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

【Rails(5.0)】 japanMapリンク使用時 js.erbファイル内のパラメータ書き方

初投稿になります。プログラミング始めて4ヶ月の初心者です。よろしくお願いします。

ポートフォリオ作成の際にjapanMapを使用して8地方ごとにイベントを検索できるようにしました。

その際、地方ごとのリンクに任意のパラメータを渡すのに少し苦労したので、自分で確認用として投稿しています。

今回は application.jsファイルにjapanMapのプログラムを記述しました。

以下が自分の書いたコードになります。

application.js
//検索ページ日本地図
$(function(){
  //8地方でリンク作成
  var areaLinks = {
    1:"/user/index?sort=hokkaido",
    2:"/user/index?sort=tohoku",
    3:"/user/index?sort=kanto",
    4:"/user/index?sort=chubu",
    5:"/user/index?sort=kinki",
    6:"/user/index?sort=chugoku_shikoku",
    7:"/user/index?sort=kyusyu_okinawa",
  };

  //8地方エリア指定
  var areas = [
    {code : 1, name: "北海道", color: "#ab86c4", hoverColor: "#dfcceb", prefectures: [1]},
    {code : 2, name: "東北",   color: "#6d93d1", hoverColor: "#91b0e3", prefectures: [2,3,4,5,6,7]},
    {code : 3, name: "関東",   color: "#f5a164", hoverColor: "#f5c09a", prefectures: [8,9,10,11,12,13,14]},
    {code : 4, name: "中部",   color: "#77e077", hoverColor: "#adedad", prefectures: [15,16,17,18,19,20,21,22,23]},
    {code : 5, name: "近畿",   color: "#ffe966", hoverColor: "#fff2a3", prefectures: [24,25,26,27,28,29,30]},
    {code : 6, name: "中国・四国",   color: "#e68ccc", hoverColor: "#f0b9e0", prefectures: [31,32,33,34,35,36,37,38,39]},
    {code : 7, name: "九州・沖縄",   color: "#de6474", hoverColor: "#f29da9", prefectures: [40,41,42,43,44,45,46,47]},
  ];

  //地図表示設定
  $("#map-container").japanMap({
    width: 600,
    areas  : areas,
    selection : "area",
    borderLineWidth: 0.25,
    drawsBoxLine : false,
    movesIslands : true,
    showsAreaName : true,
    font : "MS Mincho",
    fontSize : 13,
    fontColor :"#777",
    fontShadowColor : "white",
    onSelect : function(data){
    location.href = areaLinks[data.area.code];
  };
});

上記のこの部分がリンクになります。

var areaLinks = {
    1:"/user/index?sort=hokkaido",
    2:"/user/index?sort=tohoku",
    3:"/user/index?sort=kanto",
    4:"/user/index?sort=chubu",
    5:"/user/index?sort=kinki",
    6:"/user/index?sort=chugoku_shikoku",
    7:"/user/index?sort=kyusyu_okinawa",
};

html.erbファイルでは下記のように記述するところを

○○.html.erb
<%= link_to '◯◯', ○◯_path(:sort => 'hokkaido') %>

js.erbファイルでのlink_to の書き方がわからなかった為、

○○.js.erb
1:"/user/index?sort=hokkaido"

のように記述しました。こうすることで任意のparams[:sort]のパラメータを渡すことができました。
[?sort=hokkaido] の部分がパラメーターになります。

○○_controller.rb
def index
    if params[:sort] == 'hokkaido'
        @events = Event.where(prefecture_code: "北海道")
        @events = @events.page(params[:page]).per(6).order("id DESC")

上記がコントローラーの一部になります。
[if params[:sort] == 'hokkaido']の記述でパラメーターを区別し表示するイベントを変更しています。

初めての投稿で見にくいところ間違いなどあるかもしれませんが最後まで見ていただきありがとうございました。

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

【Ruby】メソッドのselfを戻り値にするとどうなるか?

再度、繋げて同じメソッドを使えるようになります。

実践例

class Hoge
  def foo
     self
  end
end

Hoge.new.foo
#<Hoge:0x00007f92612b2e38>

Hoge.new.foo.foo.foo.foo
#<Hoge:0x00007f92618c82a0>

このように、戻り値が同じ型なので、再度同じメソッドを使うことが可能になります。

解説

a.method b

と言う状況で戻り値がレシーバのaになります。だから、

c = a.method b

では、cにはaと同じ型のものが出力されます。

応用例

インスタンス変数を保持させ、メソッドを呼び出した回数をカウントさせます。

class Hoge
  def foo
     @a.nil? ? @a = 1 : @a = @a + 1 
     self
  end
end

Hoge.new.foo
=> #<Hoge:0x00007f92618c2080 @a=1>

Hoge.new.foo.foo.foo.foo
=> #<Hoge:0x00007f92618a0a98 @a=4>

こうすると、fooメソッドを呼び出した回数だけ@aの値が増えていっているのがわかります。

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

Rubyでの正規表現における行頭・行末の扱い

Railsでモデルのバリデーションを正規表現で、チェックしようとしたところエラーが起こったので備忘録としてメモしておきます。

環境

Ruby 2.5.1
Rails 5.2

エラーになった実装

user.rb
class User < ApplicationRecord
  validates :age, format: { with: /^[0-9]+$/, message: "は数値のみ入力可能です。"}
end

エラーの内容

The provided regular expression is using multiline anchors (^ or $), which may present a security risk. Did you mean to use \A and \z, or forgot to add the :multiline => true option?

エラーの内容としては、^,$はセキュリティのリスクがあるため、\A,\zを使えと言われているようです。
^A
$z
上記の方法ではない場合、:multiline => trueオプションをつけることで脆弱性を含むコードでも、あえてエラーを発生させないとい方法もあります。

調べたところによると、Rails4以降ではセキュリティ対策として、正規表現が厳しくなったようです。

Rubyでは特定の頭と末尾を指定したマッチを行いたい場合は以下のように実装するのが良さそうです。
:multiline => trueオプションをつける方法もありますが、バリデーション処理の場合などは、特別な理由がない限りは、指定しないほうがいいのかなと感じました。

user.rb
class User < ApplicationRecord
  validates :age, format: { with: /\A[0-9]+\z/, message: "は数値のみ入力可能です。"}
end

一応Rubyの公式リファレンスでも以下のように定義されています。

^ 行頭にマッチします。行頭とは、文字列の先頭もしくは改行の次を 意味します。
$ 行末にマッチします。 行末とは文字列の末尾もしくは改行の手前を意味します。
\A 文字列の先頭にマッチします。
\z 文字列の末尾にマッチします。

参考文献

正規表現によるバリデーションでは ^ と $ ではなく \A と \z を使おう
Rails4では正規表現が厳しくなった。
Ruby 2.7.0 リファレンスマニュアル 正規表現

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

便利❣️Rails開発がスムーズになる機能!

現在私はdocker環境下でRuby on Railsで自社サービスの開発を行っており、
dockerを使用した方ならご理解頂けると思うのですが、、、



_人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人_
> ?docker関連のコマンド反応がめっっっっっっっっっっっちゃ遅い!!!? <
 ̄^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄



特に以下のケースを確認する時に時間がかかるとイライラします。。



  • コントローラに渡すパラメーターの中身を確認したい。

  • 呼び出しているActive Recordの種類。

  • render元のファイル。



とまあいろいろあるのですが、とにかく以下から始まるコードはとにかく遅いです。
(この前migrationファイルを作成するのに約10分かかりました。。)

$ docker-compose run ~



そんな私と同じような環境で開発している、Rails初心者の方へ朗報です!
Rails panellというchromeの拡張機能はご存知でしょうか??



結論から言うとめちゃ便利です。
デバッグ関連でpryとか使用している人もこれは入れて欲しい。
以下概要です!



スクリーンショット 2020-11-08 11.16.24.png
Chrome store link
https://chrome.google.com/webstore/detail/railspanel/gjpfobpafnhjhbajcjgccbbdofdckggg



これを拡張機能に追加すると、検証ツールの一番右側に追加されて、以下のことを簡単に確認できます!
(上記画像参照。)

  • Breakdown: 処理時間の内訳(ActiveRecord, Rendering, Other)

  • Params: コントローラから参照できるparamsの内容

  • ActiveRecord: そのリクエストを処理する際に発行したSQLと処理時間

  • Rendering: ビューテンプレートごとの描画時間

(参考元:https://chopschips.net/blog/2015/03/06/rails-panel/)


なのでいちいちコンソールを立ち上げたり、pryで止めたりせずとも簡単に確認できちゃいます!
これでデバッグに係る時間を少しでも削減できますね♫


今回初投稿となり拙い文章で間違っている箇所もあるかと思いますが、
もしここまで読んでくれた方がいたらとても嬉しいです!


もっと便利な拡張機能あるよとかあれば、是非コメントで教えてください?‍♂️
以上初心者エンジニアでした!

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

【Rails/AWS】RDSのMySQLに繋がらないエラーの考えられる原因(database.yml)

はじめに

本記事は、RailsアプリをAWSにデプロイした際に発生する可能性のある
MySQLに繋がらないという事象に対する原因例を紹介します。
原因はかなり初歩的な原因でしたが、筆者はこのエラー原因が特定できずとても苦労したため、
今後同じエラーに遭遇した方の助けになれば幸いです。

開発環境

  • Ruby 2.5.1
  • Rails 5.2.4.4
  • AWS(EC2, RDS)
  • MySQL(RDS) 5.6.48

前提条件

  • RailsアプリをEC2のWebサーバー上にgitクローン済み。
  • 基本的な設定は完了済みで、rake db:create RAILS_ENV=productionのコマンドを実行する手前の状態。
  • RDSのDBインスタンスを作成済み。
  • Railsのdatabase.ymlは下記の内容です。
database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:
  socket: /tmp/mysql.sock

production:
  <<: *default
  database: データベース名
  host: <%= ENV['DATABASE_HOST_PRODUCTION'] %>
  username: <%= ENV['DATABASE_USER_NAME_PRODUCTION'] %>
  password: <%= ENV['DATABASE_PASSWORD_PRODUCTION'] %>

エラー内容

以下のコマンドだとRDSのMySQLに繋がったのですが・・・・

[ec2-user@ip-10-0-1-10 アプリのディレクトリ]$ mysql -h RDSのエンドポイント -u root -P 3306 -p

以下のコマンドだと繋がらず、MySQLに繋がらないというエラーが出ました。

[ec2-user@ip-10-0-1-10 アプリのディレクトリ]$ rake db:create RAILS_ENV=production
Can't connect to MySQL server on '10.0.1.10' (111)
Couldn't create 'データベース名' database. Please check your configuration.
rake aborted!
Mysql2::Error::ConnectionError: Can't connect to MySQL server on '10.0.1.10' (111)

Tasks: TOP => db:create
(See full trace by running task with --trace)

原因/解決策

当初はホスト名の設定ができていなかったので、エンドポイントを環境変数に入れて修正したのですが、
mysqlコマンドだと繋がるのにrakeコマンドだと繋がらないという謎の現象が発生しました。
いろいろ調べたところ、MySQLのパスワードにパスワードに"#"が入っていることが原因でした。
YAMLファイルはコメントアウトの記法が#ということで、パスワードに"#"が入っているとコメントされてしまいます。
パスワードを変更したら無事rakeコマンドが通りました。
みなさんお気を付けください。

まとめ

MySQLのパスワードに"#'を含めるのは良くない。

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

POSTMAN で画像を投稿する

はじめに

これまでテキストデータを投げることばかりしていたので
初めて画像投稿機能をやるときに、ちょっと手こずりました

方法

パラメーターに次のように記載すると出来ました!


パラメーターの名前[要素名]


なので今回は、image[cat_photo]といった具合に記載しました。
下のようにデータ登録した上でSendするといいです。
スクリーンショット 2020-11-08 9.33.48.png
データベースを確認、、、
ない!と思ったら
スクリーンショット 2020-11-08 9.41.02.png
active_storageにきちんと紐づいた形で収まってました。
スクリーンショット 2020-11-08 9.40.13.png

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

MacでVSCode Remote ContainersをCLIから直接開く

仕組み

一度VSCode Remote Containersを開いた後 cat ~/Library/Application\ Support/Code/storage.json
windowsState > lastActiveWindow > folder を見ると以下のような感じになっているのが分かります。

    "windowsState": {
        "lastActiveWindow": {
            "folder": "vscode-remote://dev-container%2B2f557365722f6e616d652f646576656c6f702f70726f6a656374/usr/src",
            "backupPath": "...",
            "remoteAuthority": "dev-container+2f557365722f6e616d652f646576656c6f702f70726f6a656374",
            "uiState": {
                "mode": 1,
                "x": 0,
                "y": 23,
                "width": 3440,
                "height": 1417
            }
        },
        "openedWindows": []
    },

この folder のURLが分かれば

$ code --folder-uri vscode-remote://dev-container%2B2f557365722f6e616d652f646576656c6f702f70726f6a656374/usr/src

このように直接CLIから立ち上げる事が可能です。

2f557365722f6e616d652f646576656c6f702f70726f6a656374 の部分は16進数文字列になっていて
これをデコードすると /User/name/develop/project となります。また %2B が含まれているのでURLエンコードされているのが分かります。
最後の /usr/srcdevcontainer.jsonworkspaceFolder で指定したパスになります。
これらを踏まえて全体のURL構成要素としては

"vscode-remote://" + URI.encode_www_form_component("dev-container+" + "/User/name/develop/project".unpack('H*')) + "/usr/src"

となっているようです。

スクリプトの作成

パスを指定したらVSCode Remote Containersを起動する為のURLを生成してくれるスクリプトを書いてみます。
指定したパス配下の .devcontainer/devcontainer.jsonworkspaceFolder を読み込んで生成してます。

main.rb
# frozen_string_literal: true
# !/usr/bin/env ruby
require 'json'

module VSCodeRemoteContainer
  class Utility
    def initialize
    end

    def generate_url(root_path)
      folder = find_workspace_folder(root_path)
      path = "dev-container+#{root_path.unpack('H*')[0]}"
      puts "vscode-remote://#{URI.encode_www_form_component(path)}#{folder}"
    end

    def find_workspace_folder(root_path)
      unless File.exist?("#{root_path}/.devcontainer/devcontainer.json")
        puts 'Not found devcontainer.json file.'
        return
      end

      config = JSON.parse(File.read("#{root_path}/.devcontainer/devcontainer.json"))
      config['workspaceFolder']
    end
  end
end

VSCodeRemoteContainer::Utility.new.generate_url(*ARGV)

上記を main.rb として保存し以下の様に実行するとURLが生成されます

$ ruby main.rb '/User/name/xxxx'
# => vscode-remote://dev-container%2B2f557365722f6e616d652f646576656c6f702f70726f6a656374/usr/src

AlfredのWorkflowsの作成

今回はghqと組み合わせて動作するWorkflowsを作成してみました。
動作イメージとして↓のようにリポジトリ名を指定して直接VSCodeをコンテナ内で起動しています。

Workflowsを作成するにあたって上記のスクリプトに追記しました。

module VSCodeRemoteContainer
  class Utility
    attr_accessor :bin_path
    def initialize
      @bin_path = ENV['GHQ_PATH'] || '/usr/local/bin'
    end

    def ghq_exists?
      !`which #{@bin_path}/ghq`.empty?
    end

    def search
      return unless ghq_exists?

      result = []
      `#{@bin_path}/ghq list --full-path`.split(/\R/).each do |d|
        Dir.foreach(d) do |path|
          next if ['.', '..'].include?(path)

          file = File.join(d, path)
          result << d if file.include?('.devcontainer')
        end
      end
      result
    end
  end
end

やってる事は ghq を使って対象のリポジトリ一覧をとってきて、
.devcontainer ディレクトリがあるリポジトリだけを返すようにしてます。

出来上がったものは
https://gist.github.com/Slowhand0309/253bb296cd7acb089601d2b32da4723b
こちらに置いております。とりあえず作ってみた程度なので不具合等見つけたら
ご連絡頂けると助かります。
zshをベースに作ってますが、お使いのシェルでお好みで修正して頂ければ :pray:

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

【RSpec】Shoulda-Matchers で独自に設定したエラーメッセージをテストする方法

shoulda-matchersを使ってカスタムメッセージのバリデーションテストをするとき、書き方がわからなくて少し詰まったのでここに共有させて頂きます。

shoulda-matchersとは何ぞや?

shoulda-matchersというのは、通常のRSpecで書くと長くなるテストが簡潔にかけるようになるgemです。

例えば以下のようなバリデーションを持ったモデルがあったとします。

user.rb
class User < ApplicationRecord
  validates :nickname, presence: true, length: { maximum: 30 }
end

このテストが以下のように一行でかけます。

spec/models/user_spec.rb
it { is_expected.to validate_length_of(:nickname).is_at_most(30) }

なかなかに便利ですね。

独自に設定したエラーメッセージをテストしたい場合

例えば以下のような、独自のエラーメッセージを持ったバリデーションを設定したとします。

user.rb
validates :email, presence: { message: 'が入っていません' }

バリデーションと同時に、独自のエラーメッセージが表示されているかということもチェックしたい場合

spec/models/user_spec.rb
it { should validate_presence_of(:email). with_message('が入っていません') }

このように書くことで独自のエラーメッセージとバリデーションを同時にチェックすることができます。

他にも使い方が色々あるので、気になる方はこちらを一読してみてください。
導入方法も書いてあります。
Shoulda-Matchers READ ME

最後まで読んでいただきありがとうございます!

日々学んだことをアプトプットしてます!なにかご指摘などあればコメントいただけますと嬉しいです!

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

ruby小文字を大文字にする

Rubyで小文字を大文字にする

半角アルファベットの小文字で構成された長さ t の文字列 y が与えられます。

文字列 y を大文字に変換して出力して下さい。

入力される値

入力は以下のフォーマットで与えられます。

y

期待する出力

y を大文字に変換した文字列を出力してください。

入力例1

qiita

出力例1

QIITA

私の答え

y = gets
puts y.upcase

今回のポイント

1行目のgetsで文字列を取得しています

2行目で取得した文字列yをupcaseメソッドで出力しています

upcaseメソッドは小文字→大文字にするメソッドです。ちなみにその逆はdowncaseメソッドです。

y = "AAAAA"
puts y.downcase
出力結果
▶︎ AAAAA

となります。

他にも小文字のみや大文字のみを逆にしてくれるswapcaseメソッドや先頭の大文字を小文字に変換してくれるcapitalizeメソッドがあります。

以上!

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