Railsで詰まったこと
前書き
neat02です。
今回はRealWorldで、APIでエンドポイントを作成しました。
環境
RealWorldとは
RailsやLaravelといったフレームワークの学習を目的に作られた、OSSのプロジェクトです。今回は、メディア記事の作成、詳細の閲覧、更新、削除のエンドポイントを作成しました。 必要なgemは 'active_model_serializers' です。
エンドポイントの作成の流れ
- モデルの作成
このようなモデルを作成します。
class CreateArticles < ActiveRecord::Migration[7.0] def change create_table :articles do |t| t.string :slug t.string :title t.string :description t.text :body t.timestamps end end end
2. コントローラーの作成 以下の通りです。
class ArticlesController < ApplicationController def create @article = Article.new(article_params) if @article.save render json: { article: ArticleSerializer.new(@article) }, status: :created else render json: { errors: @article.errors.full_messages }, status: :unprocessable_entity end end def show @article = Article.find_by(slug: params[:slug]) render json: { article: ArticleSerializer.new(@article) }, status: :ok end def update @article = Article.find_by(slug: params[:slug]) if @article.update(article_params) render json: { article: ArticleSerializer.new(@article) }, status: :ok else render json: { errors: @article.errors.full_messages }, status: :unprocessable_entity end end def destroy @article = Article.find_by(slug: params[:slug]) @article.destroy head :no_content end private def article_params params.require(:article).permit(:title, :description, :body) end end
3.ルートの設定 以下の通りです。
Rails.application.routes.draw do scope :api do resources :articles, param: :slug, only: %i[create] resources :articles, param: :slug, only: %i[show] resources :articles, param: :slug, only: %i[update] resources :articles, param: :slug, only: %i[destroy] end end
私が詰まったのがシリアライザーです。 Serializerとは、データの入出力をモデルで橋渡しをする"クラス"のことです。 Serializerはコントローラーで、JSONで出力値を定義するのに用いました。 最初クラスということを知らずに、以下のように書いてしまいました。
class ArticlesController < ApplicationController def create @article = Article.new(article_params) if @article.save render json: { article: @article }, serializer: :ArticleSerializer, status: :created ・・・
これだとrenderメソッドにSerializerが適用されません。 なぜなら、@article(オブジェクト)をSerializerに渡してないからです。 以下のように修正しました。
class ArticlesController < ApplicationController def create @article = Article.new(article_params) if @article.save render json: { article: ArticleSerializer.new(@article) }, status: :created ・・・
これで解決です。 ArticleSerializerクラスのインスタンスを作成し、それを@article(オブジェクト)に渡して、JSON形式に変換して出力できました。
修正前
{ slug: "・・・" title: "・・・・・ " description: "・・・・・" body: "・・・・・” }
修正後
{ article: { slug: "・・・" title: "・・・・・ " description: "・・・・・" body: "・・・・・” } }
模写コーディングで詰まったこと
前書き
neat02です。
模写コーディングで、個人的に気になった点について執筆出来ればと思います。
環境
リンクの制約
HTMLを書いていると必ず出てくるのがリンクです。リンクは基本、url だったり、画像を載せる際にパスを記載する際に使います。 今回とある予約フォームを模写しました。 私が実装したのは、カレンダーの日時をボタンで選択すると、
- 予約フォームに日時が自動で入力
- フォームの欄まで、スクロール
という2つです。 しかし、リンクは1つまでしか挿入できないため、以下のように書くとエラーになります。
#例 <a.href=# ,a href="./sample.html#book_time"></a>
そのため、JavaScriptでスクロールを実装しました。
function buttonClick() { target = document.getElementById("booktime"); target.innerHTML = "2023-06-12 10:00"; target.style.color = "#333"; window.scrollBy({ top: window.innerHeight, behavior: 'smooth' }); return false; }
こうすることで、ボタンを押した際にフォーム欄に時間が入力され、フォームまでスクロールすることができました。 まだ知識が定着していないので、書籍やMdnで学習していきます!!
相関サブクエリの難しさ
前書き
neat02です。
今回はSQLの相関サブクエリで、個人的に気になった点について執筆出来ればと思います。
環境
相関サブクエリとは
相関サブクエリとは、外側のメインクエリと内側のサブクエリが関連している状態を指します。つまり、メインクエリからサブクエリで指定した条件を照らし合わせて、データを抽出します。
先に相関サブクエリを使ったクエリを提示します。
ここでは、E1がメインクエリ、E2がサブクエリです。
#メインクエリ SELECT E1.gender, E1.emp_no, E1.birth_date FROM employees AS E1 WHERE E1.birth_date = ( #相関サブクエリ SELECT MIN(E2.birth_date) FROM employees AS E2 WHERE E1.gender = E2.gender );
このクエリは、各性別の最年少の従業員の性別、従業員番号、生年月日を抽出します。
サブクエリで最年少(生年月日が遅い)人を選択して、再度WHERE句でメインクエリで性別を持っている人に一行ずつ検索をかけ、メインクエリにて最年少(生年月日が遅い)の人のデータを抽出します。
しかし、相関サブクエリには重大な欠点があります。
それは抽出に時間がかかることです。
レコードが少なければいいのですが、莫大な数のレコードであれば、
一行ごとに参照して抽出する相関サブクエリは相性最悪です。
そのため、このケースでは内部結合するのが手っ取り早いです。
#メインクエリ SELECT E.gender, E.emp_no, E.birth_date FROM employees AS E #サブクエリ JOIN ( SELECT gender, MIN(birth_date) AS min_birth_date FROM employees GROUP BY gender) AS G ON E.gender = G.gender AND E.birth_date = G.min_birth_date;
これは、サブクエリが各性別の最年少の従業員を選択します。
それをもとに、もとの「employees」テーブルと結合します。結合条件は、性別と生年月日が同じことです。これによりメインクエリで、各性別の最年少の従業員の性別、従業員番号、生年月日を選択できます。
実際のデータを用いると、相関サブクエリはデータの抽出が終了せず、内部結合による抽出は約30秒で終了しました。
適切なクエリを使えるようになりたいです。
機密性のあるパスワードマネージャーを作成した
お久しぶりです。
neat02です。今回はシェルスクリプトにてパスワードマネージャーを作ってみました。
前回はブラックジャックゲームの作成手順の記述のみだったので、今回は作成中に生じた疑問点や改善点を主に執筆していけたらという風に思います。
概要
必要な機能は、
①. パスワードの追加
②. パスワードの検索
の2つがあれば最低限のものはできます。
さらに今回は機密性が高いということで、作成したパスワード(ファイルで保存)を暗号化して、検索の際には閲覧可能にしました。
③パスワードの機密性
以上3つを基に作成します。
①パスワードの追加
#Add Passwordの場合 "Add Password") read -p "サービス名を入力してください: " service_name read -p "ユーザー名を入力してください: " user_name read -p "パスワードを入力してください: " password echo "" # ディレクトリにパスワードを保存 file_name="${data_directory}/${service_name}_${user_name}.txt" echo "サービス名: $service_name" > $file_name echo "ユーザー名: $user_name" >> $file_name echo "パスワード: $password" >> $file_name # 暗号化したファイルの生成 echo $password | gpg -c --batch --yes --passphrase "$cipher_key" -o "${file_name}.gpg" $file_name # 暗号化前のファイル削除 rm $file_name echo "パスワードの追加は成功いたしました。" ;;
サービス名、ユーザー名、パスワードを入力してもらい、
サービス名、ユーザー名、パスワードが記載されたファイルを "password_data"にします。
この際、ファイルを暗号化するコマンドが長くなってしまったため、更なる学習が必要だと感じました。
②パスワードの検索
# Get Passwordの場合 "Get Password") read -p "サービス名を入力してください: " service_name # 入力されたサービス名に一致するファイルパターンを用意 file_pattern="${data_directory}/${service_name}_*.txt.gpg" # 一致ファイルが存在するかどうか確認 file_serch=$(find "$data_directory" -name "${service_name}_*.txt.gpg" 2> /dev/null) # 一致するファイルが存在しない場合 if [ ! -e $file_pattern ]; then echo "そのサービスは登録されていません" # 一致するファイルが存在する場合 else for cipher_file in $file_pattern; do decipher_file="${cipher_file%.gpg}" gpg --quiet --batch --yes --passphrase "$cipher_key" -o "$decipher_file" -d "$cipher_file" cat "$decipher_file" rm "$decipher_file" done fi ;;
サービス名を入力し、入力されたサービス名のファイルを用意する変数"file_pattern"とそれと一致するファイルを検索する変数"file_serch"を作成しました。
一致するファイルが存在する場合、そのファイルは暗号化されているため、復号しなければ内容が見れません。
そこで、暗号化されたファイルの変数、"cipher_file"と復号化されたファイルの変数"decipher_file"を用意します。
file_pattern に一致するファイルのリストをループ処理します。
ループの内容は、変数”decipher_file"で暗号化されたファイルから、復号化したファイルを生成し、 次のコードでGPGを使って暗号化されたファイルを復号化します。
そしてcat , rm で一次的に保存された復号化ファイルの内容を表示し、削除します。
ここでは暗号化と復号化のファイル(変数)を2つ用意することで、ファイルを復号化して内容を見ることができても、暗号化のファイルはそのまま残るため、機密性があります。
まとめ
- GPGのコマンドが良く分かっていない。
- 変数=処理にするコードの記述方法は果たしていいのか
- 暗号化と復号化のファイルを2つ用意することで、機密性を付与することが出来た。
普通の文系大学生がRubyでブラックジャックを作る
はじめに
こんにちは。neat02と申します。現在大学4年生でエンジニア志望です。最近は、自身のプログラミングの技術力を無さを実感し、ポートフォリオの作成を目標に日々学習しています。前置きが長くなりましたが、今回はブラックジャックを作成したので、解説していきたいと思います。
設計
プログラムを組む上で最も重要なことは、設計です。
今回作成したブラックジャックは以下の項目に沿って設計しました。
-
プレイヤーは実行者、ディーラーはCPUが自動実行する
-
実行開始時、プレイヤーとディーラーはそれぞれ、カードを2枚引く。引いたカードは画面に表示する。ただし、ディーラーの2枚目のカードは分からないようにする
-
その後、先にプレイヤーがカードを引く。プレイヤーのカードの合計値が21を超えたらプレイヤーの負け
-
プレイヤーはカードを引くたびに次のカードを引くか選択できる
-
プレイヤーがカードを引き終えたら、ディーラーは自分のカードの合計値が17以上になるまで引き続ける
-
プレイヤーとディーラーが引き終えたら勝負。カードの合計値が21により近い方が勝ち
-
Aは1 or11点として取り扱う。(合計値が21を越えないようにする)
実装
ここで考えなければならないのは、クラス分けです。
クラス分けとは、そのままの意味で役割を分担することです。
ブラックジャックは、プレイヤー,ディーラーはカードを2枚配られ、プレイヤ―は合計値が21に近づくまでカードを引き続けます。
現実世界と同様に考えるのはナンセンスではありますが、プログラム上でもカードを用意する必要があるため、カードクラスを作ります。
RANKS = %w[2 3 4 5 6 7 8 9 10 J Q K A].freeze
SUITS = %w[クローバー ハート ダイヤ スペード].freeze
attr_reader :rank, :suit
def initialize(rank, suit)
@rank = rank
@suit = suit
end
def value
case @rank
when 'A' then 11
when 'K', 'Q', 'J' then 10
else @rank.to_i
end
end
end
def initialize
@cards = []
Card::SUITS.each do |suit|
Card::RANKS.each do |rank|
@cards << Card.new(rank, suit)
end
end
@cards.shuffle!
end
def draw
@cards.pop
end
end
require_relative 'deck'
class Blackjack
def hand_value(hand)
base_value = hand.map(&:value).reduce(:+)
num_aces = hand.count { |card| card.rank == 'A' }
num_aces.times do
base_value -= 10 if base_value > 21
end
base_value
end
def display_card(card, owner)
puts "#{owner}の引いたカードは#{card.suit}の#{card.rank}です。"
end
def play
puts 'ブラックジャックを開始します。'
deck = Deck.new
player_hand = [deck.draw, deck.draw]
dealer_hand = [deck.draw, deck.draw]
display_card(player_hand[0], 'あなた')
display_card(player_hand[1], 'あなた')
display_card(dealer_hand[0], 'ディーラー')
puts 'ディーラーの引いた2枚目のカードはわかりません。'
puts "あなたの現在の得点は#{hand_value(player_hand)}です。カードを引きますか?(Y/N)"
while hand_value(player_hand) < 21
input = gets.chomp.upcase
break if input == 'N'
unless %w[Y N].include?(input)
puts 'エラー: 正しく入力してください。'
next
end
new_card = deck.draw
player_hand << new_card
display_card(new_card, 'あなた')
puts "あなたの現在の得点は#{hand_value(player_hand)}です。カードを引きますか?(Y/N)
end
puts "ディーラーの引いた2枚目のカードは#{dealer_hand[1].suit}の#{dealer_hand[1].rank}
while hand_value(dealer_hand) < 17
new_card = deck.draw
dealer_hand << new_card
display_card(new_card, 'ディーラー')
break if hand_value(dealer_hand) > 17
puts "ディーラーの現在の得点は#{hand_value(dealer_hand)}です。"
end
puts "あなたの得点は#{hand_value(player_hand)}です。"
puts "ディーラーの得点は#{hand_value(dealer_hand)}です。"
if hand_value(player_hand) > 21
puts 'あなたの負けです!'
elsif hand_value(dealer_hand) > 21 || hand_value(player_hand) > hand_value(dealer_hand)
puts 'あなたの勝ちです!'
elsif hand_value(player_hand) == hand_value(dealer_hand)
puts '引き分けです!'
else
puts 'あなたの負けです!'
end
puts 'ブラックジャックを終了します。'
end
end
Blackjack.new.play
ディーラーの引いたカードはダイヤのAです。
ディーラーの引いた2枚目のカードはわかりません。
あなたの現在の得点は11です。カードを引きますか?(Y/N)
Y
あなたの引いたカードはハートのQです。
ディーラーの引いた2枚目のカードはダイヤの7でした。
あなたの得点は21です。
ディーラーの得点は18です。
あなたの勝ちです!
ブラックジャックを終了します。