Railsで詰まったこと

前書き

neat02です。
今回はRealWorldで、APIでエンドポイントを作成しました。

環境

Rails 7.0.5 (API)

RealWorldとは

RailsやLaravelといったフレームワークの学習を目的に作られた、OSSのプロジェクトです。今回は、メディア記事の作成、詳細の閲覧、更新、削除のエンドポイントを作成しました。 必要なgemは 'active_model_serializers' です。

エンドポイントの作成の流れ

  1. モデルの作成
    このようなモデルを作成します。
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です。
模写コーディングで、個人的に気になった点について執筆出来ればと思います。

環境

VSCode

リンクの制約

HTMLを書いていると必ず出てくるのがリンクです。リンクは基本、url だったり、画像を載せる際にパスを記載する際に使います。 今回とある予約フォームを模写しました。 私が実装したのは、カレンダーの日時をボタンで選択すると、

  1. 予約フォームに日時が自動で入力
  2. フォームの欄まで、スクロール

という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の相関サブクエリで、個人的に気になった点について執筆出来ればと思います。

環境

MySQL

相関サブクエリとは

相関サブクエリとは、外側のメインクエリと内側のサブクエリが関連している状態を指します。つまり、メインクエリからサブクエリで指定した条件を照らし合わせて、データを抽出します。 先に相関サブクエリを使ったクエリを提示します。
ここでは、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に近づくまでカードを引き続けます。

現実世界と同様に考えるのはナンセンスではありますが、プログラム上でもカードを用意する必要があるため、カードクラスを作ります。

 

class Card
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
 
こんな感じです。トランプのカードは数字とスーツ(柄)で構成されているため、要素を書き出します。文字のままだと、得点の計算が出来ないので、J, Q, Kは10、Aは11に変換します。
 
次にデッキクラスを作ります。
さっきのカードクラスはカードを説明しただけなので、カードを配る(引く)機能を付けます。
require_relative 'card'
class Deck
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
 
ここでは、数字とスーツの要素を@cardという変数に入れています。そしてその要素がランダムになるようshuffle!して、カードを引けるようにします。
 
 
最後にゲームの流れを説明したゲームクラスを作ります。
 
 
require_relative 'card'
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
" unless hand_value(player_hand) >= 21

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
 
 
コンソールで動かすとこんな感じになります。
あなたの引いたカードはハートの2です。
ディーラーの引いたカードはダイヤのAです。
ディーラーの引いた2枚目のカードはわかりません。
あなたの現在の得点は11です。カードを引きますか?(Y/N
Y
あなたの引いたカードはハートのQです。
ディーラーの引いた2枚目のカードはダイヤの7でした。
あなたの得点は21です。
ディーラーの得点は18です。
あなたの勝ちです!
ブラックジャックを終了します。
 

締め

以上がブラックジャックの説明になります。
カードとデッキのコードは非常に簡略化(人に教えてもらった)出来たのは良かったと思います。ゲームの流れについては、冗長的なコードが多いと思うのでそこを改善していければと思います。
今後もエンジニアの就職目指していくので、応援して頂けると幸いです。