- 投稿日:2020-11-08T23:43:37+09:00
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
私の答え(誤った答えです)
rubyarray = { "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という出力結果にする方法は様々あると思いますが当てはまったら部分的に置換するという方法がこれ以上できませんでした。もし分かる方、お優しい方がいらっしゃいましたらご教授頂けますと幸いです。
以上
- 投稿日:2020-11-08T22:01:39+09:00
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以外の記事を参考に無理やり作った形ですので、正解は違うと思います。
- 投稿日:2020-11-08T21:27:47+09:00
既存のRailsアプリのローカル環境にDockerを導入する方法【Rails6 / MySQL8】
はじめに
本記事は私が既存のポートフォリオアプリのローカル環境に
Docker
を導入した際の手順になります。
Docker
の公式ドキュメントはPostgreSQL
での手順解説になっているためMySQL
になると公式ドキュメントの手順解説はなく、
また、Rails6
での情報もRails5
と比べるとやはり少なく、結果、Rails6
+MySQL
という組み合わせでの情報がとても少なかったです。
そのため、私自身様々なエラーが出て試行錯誤しましたので、本記事がこれからRails6
+MySQL
という組み合わせでDocker
を導入する方の助けになれば幸いです。
なお、筆者は就職活動中の実務未経験の初学者のため、誤っている点などあるかもしれません。
その際はコメントにて教えて頂けると幸いです。前提条件
Mac版 DockerDesktop
のインストールが完了していることDocker
が起動済みであること※筆者の場合、上記インストールと起動は「ゼロからはじめるDockerによるアプリケーション実行環境構築」を観ながらやりました。
Docker
の全体像を学ぶ上でもとてもわかりやすく、おすすめです。バージョン
- Ruby 2.6.5
- Rails 6.0.3.2
- MySQL 8.0.21
手順1 Dockerfile作成
既存アプリケーションのルートディレクトリに
Dockerfile
を作成し、下記のように記述します。
※今回はアプリのディレクトリ名をsample_appとします。
ここはあなたのアプリのディレクトリ名に置き換えてください。DockerfileFROM 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.ymlversion: '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.ymldefault: &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を導入することができました。
記事を書いて下さった方々、動画教材作成者の小島様、ありがとうございます。
- 投稿日:2020-11-08T21:14:41+09:00
【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が採用されているようです。
- 投稿日:2020-11-08T21:11:38+09:00
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
これでできるらしい
とはいえまだまだ謎だらけ、勢いあまりすぎたかな…?
随時更新していきますね。
- 投稿日:2020-11-08T19:25:40+09:00
Ruby 基礎
この記事は完全に個人のRuby開発の際に使用するためだけに作っている。
参考にしていただいてもいいのだが、あくまで個人用という事だけわかって頂きたい。
*執筆者はPHP学習済みなので、飛ばす部分があるが大目に見て欲しい開発環境
AWS cloud9
理由:パソコンにもうファイルが入れられないし、USBなど外部に入れるとあとあとメンドくさい可能性があるからRubyメモ開始
まつもとゆきひろ氏が作った言語。
最近ベンチャー企業とかでめちゃくちゃ使われている。
比較的書きやすい言語環境構築
$ gem install rails -v 5.2.4 $ rails -v Rails 5.2.4Ruby基礎文法
出力
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"] #20age = {:kaiseiblog=>20, :daniel=>48, :roland=>90} puts age[:kaiseiblog] #20if文
なんか後にコロンが書かなくて良いとかいう優しさを見せるRuby
kaiseiblog_pv = 8000 if kaiseiblog_pv < 5000 puts "アマチュアブロガー" end if kaiseiblog_pv >= 5000 puts "プロブロガー" endkaiseiblog = "Kaisei" if kaiseiblog == "Abe" puts "この人のブログはAbemaです。" elsif kaiseiblog == "Kaisei" puts "この人のブログはkaiseiblogです。" else puts "誰だお前" endinput系
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("プログラミング")多分色々コンストラクトとかあると思うが省略。
- 投稿日:2020-11-08T19:22:07+09:00
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.rbRails.application.routes.draw do resources :qiita_items, only: %i(index) endapp/controllers/qiita_items_controller.rbclass QiitaItemsController < ApplicationController def index response_json = QiitaApiClient.get_items # レスポンスを簡略化するため、titleプロパティのみ返すようにしている render json: response_json.map {|item| item.slice('title') } end endlib/qiita_api_client.rbclass 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.encqiita: # 取得したトークンをセットする token: xxxxxxx$ rails c ### 取得したトークンが表示されればOK > Rails.application.credentials.qiita[:token] => 'xxxxxxx'ルーティングの追加
検証で利用する
localhost:3000/qiita_items
のエンドポイントを作成します。config/routes.rbRails.application.routes.draw do resources :qiita_items, only: %i(index) endlib配下のクラスを読み込むようにする
今回はQiita APIと連携するクラスを
lib
配下に作成します。
lib
配下のクラスが読み込まれるようにするため以下のように修正します。config/application.rbmodule RailsApiClient class Application < Rails::Application # 以下を追加 config.paths.add 'lib', eager_load: true end endnet/httpを利用した外部APIとの連携方法
ここからは順を追って実装について紹介していきます。
シンプルな方法
Qiita APIと連携するクラスを作成せず、ロジック2を直接記述するパターンです。
app/controllers/qiita_items_controller.rbclass 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リクエストの流れについて改めてまとめると以下のようになります。
Net::HTTP.new
でHTTPのクライアントのオブジェクトを作成Net::HTTP::Get.new
でGETリクエストのオブジェクトを作成- HTTPクライアントを利用してGETのリクエスト
Qiita APIと連携する専用クラスを作成する
QiitaApiClient.get_items
を呼ぶことでデータが取得できるようにします。
lib
配下にQiitaApiClient
クラスを作成し、API連携のロジックを移行します。lib/qiita_api_client.rbclass 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 endAPI連携のロジックを
QiitaApiClient
に移行したので呼び出す側は以下のようになります。app/controllers/qiita_items_controller.rbclass 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.rbclass 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.rbclass 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.rbclass 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.rbclass 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.rbclass 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.rbclass 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)やってます。フォローしてもらえるとうれしいです!
- 投稿日:2020-11-08T19:20:44+09:00
【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
の画面ではこのようなエラーメッセージがでました。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.yml
のscript:
に記述すれば「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すると、
なんと値が返ってきました!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
でもテストが動きました。
データベースのエラーはなかなか原因にたどり着けなく苦労しました。無事にこのバッジをReadmeにつけることができました!!
- 投稿日:2020-11-08T19:15:15+09:00
【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 jqueryRails5以前の導入方法ではjquery-railsというGemをインストールするのが基本線のようですが、Webpackerで管理する際はyarnコマンドを使用してインストールします。
yarnとは
JavaScriptのパッケージマネージャー。Node.jsで動作するパッケージを管理する。
Node.jsとは
本来フロントサイド開発用の言語であるJavaScriptをサーバーサイドで使うための「環境」のこと
Node.jsのおかげで簡単にAjax対応ができたりする。つまり、Webpackerという翻訳機にjQueryという言語をyarnというサーバー用の説明書で登録した、という感じでしょうか(違ってたらごめんなさい)
Webpackの設定
Webpackの設定ファイルでjQueryを管理下として認定します。
config/webpack/environment.jsconst { environment } = require('@rails/webpacker') // 以下追記 const webpack = require('webpack') environment.plugins.prepend('Provide', new webpack.ProvidePlugin({ $: 'jquery/src/jquery', jQuery: 'jquery/src/jquery' }) ) // ここまで module.exports = environmentapplication.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要素のテキストに”成功!!”を代入するようになっています。
(成功例)
このように「テスト」ではなく「成功!!」と表示されていれば動作確認は完了です。おわりに
いろいろな記事を調べてgemを導入したりしていたのですが、Webpackerを使えば簡単に実装できました。
特に今回、JavaScriptとjQueryを調べていく中で、Node.jsのことを表面的にでも理解できたのはよかったかなと思います。
参考にさせていただいた記事
エンジニアの入り口 > 初心者向け!3分で理解するNode.jsとは何か?
Ruby / Rails 初学者向けの記事を書いています。
今後も週3〜4記事ペースで更新していきたいと思いますので、初学者のみなさん、ぜひフォローお願いします!!最後までお読みいただきありがとうございました!
✔︎
- 投稿日:2020-11-08T18:55:17+09:00
【ActiveAdmin】デフォルトのcreateとupdateの処理をカスタマイズしたい
- 投稿日:2020-11-08T18:18:46+09:00
【ActiveAdmin】select_boxで表示するcollectionのscopeを指定したい
- 投稿日:2020-11-08T17:55:51+09:00
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のインストールです。
Gemfilegroup :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.rbclass 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を導入するための設定が完了しました!
- 投稿日:2020-11-08T16:50:05+09:00
備忘録:Railsによるコメント機能実装
はじめに
Rubu on Railsでオリジナルアプリを作成しています。そのアプリにコメント機能を実装しました。そのため忘れないように書き記します。
手順
・commentモデル、テーブルの作成
・commentsコントローラーとルーティングの設定
・各コントローラーのアクション設定commentモデル、テーブルの作成
はじめに下記のようにターミナルでcommentモデルを作成します。
rails g model commentその後マイグレーションファイルに、textカラムを追加します(このtextカラムにはコメントが保存されます)。
2020*********_create_comments.rbclass 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 enduserとdeskを外部キー設定します。このコメントは誰が、どのdesk(画像)にしたものか管理するためです。同様にcommentモデルにもバリデーションの他、アソシエーションを設定する必要があります。
desk.rbvalidates :text, presence: true belongs_to :user belongs_to :deskuserモデル、deskモデルにもアソシエーションを追加します。なお、今回のバリデーションはコメントが空だと保存できないような設定です。
commentsコントローラーとルーティングの設定
ターミナルでコメントのコントローラーを作成します。この時、コントローラー名は複数形にする(いつもどっちだっけ?って悩んでます?)。
rails g controller commentsお次は作成したcommentsコントローラーにcreateアクションを設定します。このとき、中身は空で一旦OKです。
comments_controller.rbdef create end最後にルーティングを。コメントはdesk(画像)と紐付けします。なのでネスト(入れ子構造)にします。
route.rbresources :desks do resources :comments, only: :create endくどくなりますが、どの画像に対するコメントなのかをパスから判断できるようにすることが重要です。
各コントローラーのアクション設定
今回はdesks(画像)コントローラーのshowアクションに対するレスポンスページでコメント保存、表示します(元々このページは、投稿された画像の詳細ページです)。なので、まずはdeskコントローラーのshowアクションに@commentのインスタンス変数を定義します。
desks.controller.rbdef 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.rbdef 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以上
- 投稿日:2020-11-08T14:53:49+09:00
stripeを使いショッピングサイトを作ろう!(購入編)
プチ宣伝
https://www.code-sell.net/
コードを販売できるサービスを作りました!いらないコードがある方はぜひ使ってく見てください。ちなみにこの記事で説明するstripeを使用しています。初めに
こんにちは!今回はrailsとstripeを使い簡単?にECサイトを作っていきます。少し前自分もrailsとstripeで コードを販売するサイトを作ったのですがstripeの情報が少なすぎて非常に苦労しました。正確には情報自体はたくさんあるのですが実践的な情報が少なくただただ公式ドキュメントのようにコードを並べちょこっと説明するみたいなものばっかりでした。今回は実際にサイトを作っていきます。
使う技術・作るもの
noteみたいな記事を購入できるもの
rails
ruby
stripestripeってそもそもなに?
決済システムです。payjpとくらべ手数料が安かったり送金機能があったりします。
全体像
Customer(顧客): 購入者でありお金を支払う方。上の図で緑。
Platform(プラットフォーム): 今から作るECサイト。サービスを提供するところ。
Connected accounts: プラットフォームを利用してサービスを提供し、入金を受ける方・販売者(子アカウントとも呼ばれます)。上の図のピンク。アカウントのタイプ
stripeにはStandardとCustomという子アカウント(販売者)のタイプがあります。特徴・登録方法が違うので目を通りておきましょう。
Standard
開発コスト(手間):簡単
ユーザー視点:微妙
何かあった時の責任:販売者(子アカウント)
おすすめ度:中これは実装するのが非常に簡単なタイプです。あとで書きますが登録フォームやシステムはほとんどstripe側がやってくれます。僕たちはその登録フォームのリンクを張り付けちょこっとコピペでコントローラーに書くだけです。なにかあった時(マイナス残高など)も僕たちではなく利用しているユーザー側の責任となります。ただこの方法だとユーザーにstripeを使っていることがしっかり伝わってしまいます。登録フォームは完全にstripeが作っているしStripe の管理画面(ダッシュボード)へ、販売者がアクセスできるようになります。デザインも変更できません。
Custom
開発コスト(手間):難
ユーザー視点:いい
何かあった時の責任:プラットフォーム(開発者)
おすすめ度:高これは実装するのが難しいタイプです。登録フォームもstripeに送信するシステムもダッシュボードも自分で作ります。何かあった時も自分の責任です。ただ登録から管理画面まですべて自分のサイトで完結します。デザインももちろん自由です。
準備編
ながなが説明してきましたがとりあえず細かいことは作って覚えましょう。
今回作るのは単発の購入のサイトです。定期支払などもできますがそれは別の記事でやっていたので...。登録してAPIキーをもらう
新規登録
APIキーを取得する画面gemをインストール
gemfilegem "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.rbRails.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:integercontent...内容、商品
price...値段rails db:migrateこれでscaffoldができたと思います。
購入機能
購入機能は意外に簡単です。とりあえず最初に作ってしまいましょう。
routes.rbpost "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.rbclass 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を導入してマイページを作りスタンダードアカウントやカスタムアカウントの作り方を説明していこうと思います。
大変なので結構先になるかもしれません。
- 投稿日:2020-11-08T14:53:49+09:00
第1回 railsでショッピングサイトを作ろう!(購入編)
プチ宣伝
https://www.code-sell.net/
コードを販売できるサービスを作りました!いらないコードがある方はぜひ使ってく見てください。ちなみにこの記事で説明するstripeを使用しています。初めに
こんにちは!今回はrailsとstripeを使い簡単?にECサイトを作っていきます。少し前自分もrailsとstripeで コードを販売するサイトを作ったのですがstripeの情報が少なすぎて非常に苦労しました。正確には情報自体はたくさんあるのですが実践的な情報が少なくただただ公式ドキュメントのようにコードを並べちょこっと説明するみたいなものばっかりでした。今回は実際にサイトを作っていきます。
使う技術・作るもの
noteみたいな記事を購入できるもの
rails
ruby
stripestripeってそもそもなに?
決済システムです。payjpとくらべ手数料が安かったり送金機能があったりします。
全体像
Customer(顧客): 購入者でありお金を支払う方。上の図で緑。
Platform(プラットフォーム): 今から作るECサイト。サービスを提供するところ。
Connected accounts: プラットフォームを利用してサービスを提供し、入金を受ける方・販売者(子アカウントとも呼ばれます)。上の図のピンク。アカウントのタイプ
stripeにはStandardとCustomという子アカウント(販売者)のタイプがあります。特徴・登録方法が違うので目を通りておきましょう。
Standard
開発コスト(手間):簡単
ユーザー視点:微妙
何かあった時の責任:販売者(子アカウント)
おすすめ度:中これは実装するのが非常に簡単なタイプです。あとで書きますが登録フォームやシステムはほとんどstripe側がやってくれます。僕たちはその登録フォームのリンクを張り付けちょこっとコピペでコントローラーに書くだけです。なにかあった時(マイナス残高など)も僕たちではなく利用しているユーザー側の責任となります。ただこの方法だとユーザーにstripeを使っていることがしっかり伝わってしまいます。登録フォームは完全にstripeが作っているしStripe の管理画面(ダッシュボード)へ、販売者がアクセスできるようになります。デザインも変更できません。
Custom
開発コスト(手間):難
ユーザー視点:いい
何かあった時の責任:プラットフォーム(開発者)
おすすめ度:高これは実装するのが難しいタイプです。登録フォームもstripeに送信するシステムもダッシュボードも自分で作ります。何かあった時も自分の責任です。ただ登録から管理画面まですべて自分のサイトで完結します。デザインももちろん自由です。
準備編
ながなが説明してきましたがとりあえず細かいことは作って覚えましょう。
今回作るのは単発の購入のサイトです。定期支払などもできますがそれは別の記事でやっていたので...。登録してAPIキーをもらう
新規登録
APIキーを取得する画面gemをインストール
gemfilegem "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.rbRails.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:integercontent...内容、商品
price...値段rails db:migrateこれでscaffoldができたと思います。
購入機能
購入機能は意外に簡単です。とりあえず最初に作ってしまいましょう。
routes.rbpost "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.rbclass 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を導入してマイページを作りスタンダードアカウントやカスタムアカウントの作り方を説明していこうと思います。
大変なので結構先になるかもしれません。
- 投稿日:2020-11-08T14:53:49+09:00
railsでショッピングサイトを作ろう!(購入編)
プチ宣伝
https://www.code-sell.net/
コードを販売できるサービスを作りました!いらないコードがある方はぜひ使ってく見てください。ちなみにこの記事で説明するstripeを使用しています。初めに
こんにちは!今回はrailsとstripeを使い簡単?にECサイトを作っていきます。少し前自分もrailsとstripeで コードを販売するサイトを作ったのですがstripeの情報が少なすぎて非常に苦労しました。正確には情報自体はたくさんあるのですが実践的な情報が少なくただただ公式ドキュメントのようにコードを並べちょこっと説明するみたいなものばっかりでした。今回は実際にサイトを作っていきます。
使う技術・作るもの
noteみたいな記事を購入できるもの
rails
ruby
stripestripeってそもそもなに?
決済システムです。payjpとくらべ手数料が安かったり送金機能があったりします。
全体像
Customer(顧客): 購入者でありお金を支払う方。上の図で緑。
Platform(プラットフォーム): 今から作るECサイト。サービスを提供するところ。
Connected accounts: プラットフォームを利用してサービスを提供し、入金を受ける方・販売者(子アカウントとも呼ばれます)。上の図のピンク。アカウントのタイプ
stripeにはStandardとCustomという子アカウント(販売者)のタイプがあります。特徴・登録方法が違うので目を通りておきましょう。
Standard
開発コスト(手間):簡単
ユーザー視点:微妙
何かあった時の責任:販売者(子アカウント)
おすすめ度:中これは実装するのが非常に簡単なタイプです。あとで書きますが登録フォームやシステムはほとんどstripe側がやってくれます。僕たちはその登録フォームのリンクを張り付けちょこっとコピペでコントローラーに書くだけです。なにかあった時(マイナス残高など)も僕たちではなく利用しているユーザー側の責任となります。ただこの方法だとユーザーにstripeを使っていることがしっかり伝わってしまいます。登録フォームは完全にstripeが作っているしStripe の管理画面(ダッシュボード)へ、販売者がアクセスできるようになります。デザインも変更できません。
Custom
開発コスト(手間):難
ユーザー視点:いい
何かあった時の責任:プラットフォーム(開発者)
おすすめ度:高これは実装するのが難しいタイプです。登録フォームもstripeに送信するシステムもダッシュボードも自分で作ります。何かあった時も自分の責任です。ただ登録から管理画面まですべて自分のサイトで完結します。デザインももちろん自由です。
準備編
ながなが説明してきましたがとりあえず細かいことは作って覚えましょう。
今回作るのは単発の購入のサイトです。定期支払などもできますがそれは別の記事でやっていたので...。登録してAPIキーをもらう
新規登録
APIキーを取得する画面gemをインストール
gemfilegem "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.rbRails.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:integercontent...内容、商品
price...値段rails db:migrateこれでscaffoldができたと思います。
購入機能
購入機能は意外に簡単です。とりあえず最初に作ってしまいましょう。
routes.rbpost "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.rbclass 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を導入してマイページを作りスタンダードアカウントやカスタムアカウントの作り方を説明していこうと思います。
大変なので結構先になるかもしれません。
- 投稿日:2020-11-08T14:14:54+09:00
Railsで RoutingError がFATALレベルでログに出てくることへ対処(したかった)
概要
RoutingError
がFATALレベルでログ出力される点が困っている。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
- https://github.com/rails/rails/tree/v6.0.0/actionpack/lib/action_dispatch/middleware
- https://github.com/rack/rack/tree/master/lib/rack
これらのクラスには
#call
メソッドがあり、上側のクラスはひとつ下のクラスにリクエストを渡してレスポンスを受け取るという入れ子構造になっている。もちろんただ#call
を呼ぶだけでなく、前後にリクエスト・レスポンスを編集したり、例外の送出や捕捉なども必要に応じてしていて、それぞれが自分の役割を果たしている。※入れ子の順序は MiddlewareStack が管理するので、各クラスは自分が誰を呼び出すか知っている必要は無い。
DebugExceptions
肝心のこのアプリが何をしているのか、中身を読んだ。
ActionDispatch::DebugExceptions#calldef 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
- すぐ次のアプリにリクエストを渡してレスポンスを受け取る
- レスポンスヘッダーに
X-Cascade: pass
が設定されていたらRoutingError
を送出する- 処理中に例外が起きていれば捕捉し、必要に応じて処理をする
- 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など)のほうでエラーを吐き出している。
ログ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 ifconfig.consider_all_requests_local
is set totrue
. Ifconfig.action_dispatch.show_exceptions
is set tofalse
, 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行程度のものをベースにした。以下にコード全文を載せる。
クリックして展開
Gemfilesource "https://rubygems.org" gem "railties", "6.0.0"bin/railsAPP_PATH = File.expand_path('../config/application', __dir__) require 'rails/commands'config.rurequire_relative 'config/environment' run Rails.applicationconfig/environment.rbrequire_relative 'application' Rails.application.initialize!config/application.rbrequire 'action_controller/railtie' class MiniApp < Rails::Application config.logger = ::Logger.new(STDOUT) # ログをターミナル上で見れるように endconfig/routes.rbRails.application.routes.draw do root to: 'mini#index' endapp/controllers/mini_controller.rbclass 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/
にアクセスすれば試せる。
- 投稿日:2020-11-08T14:10:27+09:00
【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名やページ内に表示したい文字列等は適当に置き換えてください。実装後のイメージ
※スタイルはかなり適当なのでご自身で整えてください
実装していきましょう!!
①モデルとカラムの作成
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.rbhas_many :user_rooms has_many :chats has_many :rooms, through: :user_roomsroom.rbhas_many :chats has_many :user_roomsuser_room.rbbelongs_to :user belongs_to :roomchat.rbbelongs_to :user belongs_to :roomthroughについて上手く説明できる自信がないので下記のQiita記事を参考にしてみてください☟
【初心者向け】丁寧すぎるRails『アソシエーション』チュートリアル【幾ら何でも】【完璧にわかる】?④ルーティングの作成
routes.rbget 'chat/:id' => 'chats#show', as: 'chat' resources :chats, only: [:create]⑤users_controllerへ記述
users_controller.rbdef 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.rbdef 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なし 複数検索機能の実装 に関する筆者の記事
- 投稿日:2020-11-08T14:05:27+09:00
【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.erb1:"/user/index?sort=hokkaido"のように記述しました。こうすることで任意のparams[:sort]のパラメータを渡すことができました。
[?sort=hokkaido] の部分がパラメーターになります。○○_controller.rbdef index if params[:sort] == 'hokkaido' @events = Event.where(prefecture_code: "北海道") @events = @events.page(params[:page]).per(6).order("id DESC")上記がコントローラーの一部になります。
[if params[:sort] == 'hokkaido']の記述でパラメーターを区別し表示するイベントを変更しています。初めての投稿で見にくいところ間違いなどあるかもしれませんが最後まで見ていただきありがとうございました。
- 投稿日:2020-11-08T13:56:17+09:00
【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の値が増えていっているのがわかります。
- 投稿日:2020-11-08T12:17:59+09:00
Rubyでの正規表現における行頭・行末の扱い
Railsでモデルのバリデーションを正規表現で、チェックしようとしたところエラーが起こったので備忘録としてメモしておきます。
環境
Ruby 2.5.1
Rails 5.2
エラーになった実装
user.rbclass 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.rbclass User < ApplicationRecord validates :age, format: { with: /\A[0-9]+\z/, message: "は数値のみ入力可能です。"} end一応Rubyの公式リファレンスでも以下のように定義されています。
^ 行頭にマッチします。行頭とは、文字列の先頭もしくは改行の次を 意味します。
$ 行末にマッチします。 行末とは文字列の末尾もしくは改行の手前を意味します。
\A 文字列の先頭にマッチします。
\z 文字列の末尾にマッチします。参考文献
正規表現によるバリデーションでは ^ と $ ではなく \A と \z を使おう
Rails4では正規表現が厳しくなった。
Ruby 2.7.0 リファレンスマニュアル 正規表現
- 投稿日:2020-11-08T11:25:18+09:00
便利❣️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
とか使用している人もこれは入れて欲しい。
以下概要です!
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
で止めたりせずとも簡単に確認できちゃいます!
これでデバッグに係る時間を少しでも削減できますね♫
今回初投稿となり拙い文章で間違っている箇所もあるかと思いますが、
もしここまで読んでくれた方がいたらとても嬉しいです!
もっと便利な拡張機能あるよとかあれば、是非コメントで教えてください?♂️
以上初心者エンジニアでした!
- 投稿日:2020-11-08T11:08:11+09:00
【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.ymldefault: &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のパスワードに"#'を含めるのは良くない。
- 投稿日:2020-11-08T09:43:49+09:00
POSTMAN で画像を投稿する
- 投稿日:2020-11-08T04:55:07+09:00
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/src
はdevcontainer.json
のworkspaceFolder
で指定したパスになります。
これらを踏まえて全体のURL構成要素としては"vscode-remote://" + URI.encode_www_form_component("dev-container+" + "/User/name/develop/project".unpack('H*')) + "/usr/src"となっているようです。
スクリプトの作成
パスを指定したらVSCode Remote Containersを起動する為のURLを生成してくれるスクリプトを書いてみます。
指定したパス配下の.devcontainer/devcontainer.json
のworkspaceFolder
を読み込んで生成してます。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/srcAlfredの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
をベースに作ってますが、お使いのシェルでお好みで修正して頂ければ
- 投稿日:2020-11-08T00:42:06+09:00
【RSpec】Shoulda-Matchers で独自に設定したエラーメッセージをテストする方法
shoulda-matchersを使ってカスタムメッセージのバリデーションテストをするとき、書き方がわからなくて少し詰まったのでここに共有させて頂きます。
shoulda-matchersとは何ぞや?
shoulda-matchersというのは、通常のRSpecで書くと長くなるテストが簡潔にかけるようになるgemです。
例えば以下のようなバリデーションを持ったモデルがあったとします。
user.rbclass User < ApplicationRecord validates :nickname, presence: true, length: { maximum: 30 } endこのテストが以下のように一行でかけます。
spec/models/user_spec.rbit { is_expected.to validate_length_of(:nickname).is_at_most(30) }なかなかに便利ですね。
独自に設定したエラーメッセージをテストしたい場合
例えば以下のような、独自のエラーメッセージを持ったバリデーションを設定したとします。
user.rbvalidates :email, presence: { message: 'が入っていません' }バリデーションと同時に、独自のエラーメッセージが表示されているかということもチェックしたい場合
spec/models/user_spec.rbit { should validate_presence_of(:email). with_message('が入っていません') }このように書くことで独自のエラーメッセージとバリデーションを同時にチェックすることができます。
他にも使い方が色々あるので、気になる方はこちらを一読してみてください。
導入方法も書いてあります。
Shoulda-Matchers READ ME最後まで読んでいただきありがとうございます!
日々学んだことをアプトプットしてます!なにかご指摘などあればコメントいただけますと嬉しいです!
- 投稿日:2020-11-08T00:41:05+09:00
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メソッドがあります。
以上!