くま's Tech系Blog

基本的には技術で学んだことを書き留めようと思います。雑談もやるかもね!

Vue.jsでのFormの送信について

今回はVue.jsでのFormの送信について記載します。

自分で実装する中で想定通りにはいかない部分もあったので備忘録としてまとめようと思います。

@submitとは?

まず、@submitとはなんでしょうか?

<template>
  <div>
    <form @submit="handleSubmit">
      <label for="username">ユーザー名:</label>
      <input type="text" id="username" v-model="username" />

      <label for="password">パスワード:</label>
      <input type="password" id="password" v-model="password" />

      <button>ログイン</button>
    </form>
  </div>
</template>

上記のようなtemplateを例にします。

ここでは、ログインボタンをタップすると、handleSubmitの処理が行われます。

buttonにクリックアクションをつけていないのにと思ったかもしれません。 buttonタグにはsubmitresetbuttonを指定することができますが、何も指定していないとsubmitがデフォルトで設定されていることになります。 submitはフォームのデータをサーバーへ送信します。

そして、@submitは、formが送信されたときに呼び出されるカスタムイベントハンドラーです。 よって、ボタンをタップしたらhandleSubmitが実行されます。

例えば、buttonに@clickを追加することもできます。 ただし、メソッドが2回実行される可能性があるため、不要な処理が行われる恐れがあります。 そのため、通常はフォームの送信イベントだけを監視する@submitを使うのが適切だと思います。

@submitのイベント修飾子

@submitを使用すれば、formの内容を送信することができますが、1つ問題点がありました。

結果を含んだレスポンスが受け取れませんでした。

axiosを使用してバックエンドで処理を行った結果を受け取る予定でしたが、成功か失敗かわかりませんでした。

調べてみると画面がリロードされているよう...

さらに調べると、リロードされるのはImplicit submissionというHTMLの仕様のようでした。

submitボタンは無いがフォームのinput要素が1つだけの場合、もしくはフォームのinput要素が複数あるが、有効なsubmitボタンが存在する場合はImplicit submissionが発生すると記載があります。

なので、リロードしないようにする処理にする必要がありました。

ここでイベント修飾子を設定します。 イベントに修飾子をつけることでイベントハンドリングが簡単にできるようになります。 修飾子の書き方は@イベント.修飾子というように、イベントの後に「.」で繋いで記述します。また修飾子は複数繋げて記述することも可能です。

今回の場合には「@submit.prevent="handleSubmit」のようにprevent修飾子を使用することでリロードを防ぐことができます。

その他のイベント修飾子は参照に載せているVueのドキュメントに一覧が載っています。

最後に

HTMLの面とVueの面から確認するべき内容がありました。

当たり前ですが、Vue.jsで実装する上でHTMLの知識は前提として必要ですね(久々なので思い出さないと)

参照

macoblog.com

developer.mozilla.org

v2.ja.vuejs.org

html.spec.whatwg.org

iOSのWebViewについて

今回はiOSでのWebViewについてまとめます

AndroidのWebViewについては下記まとめていますので、気になる方はみてください!!

kumaskun.hatenablog.com

※今回はUIkitです。SwiftUIはこの記事に追加するか、別で記事にします🙇

Webページを表示させる

Webページを表示させるにはWKWebViewを使用します。

今回、レイアウトはUIViewにWKWebViewをaddSubViewします。

下記がWKWebViewの基本的な使い方です。

WKWebView を利用する際はUIKitとWebKitをimportします。 WKWebView のinitializeはCGRectとWKWebViewConfigurationを引数にとります。 該当するURLページを開くには、WKWebView の loadメソッドを実行します。

import UIKit
import WebKit

class WebViewController: UIViewController {

    var webView: WKWebView!

    override func viewDidLoad() {
        super.viewDidLoad()

        setupWebView()

        let url = URL(string:"https://www.apple.com")
        // URLRequestオブジェクトを生成
        let myRequest = URLRequest(url: url!)

        // URLをWebView にロード
        webView.load(myRequest)
    }

    private func setupWebView() {
        let userContentController = WKUserContentController()
        userContentController.add(self, name: "testCallBack")
        let webConfiguration = WKWebViewConfiguration()
        
        let customFrame = CGRect(
            origin: CGPoint.zero,
            size: webViewContainer.frame.size
        )
        
        webConfiguration.userContentController = userContentController
        
        webView = WKWebView(frame: customFrame, configuration: webConfiguration)
        webView.translatesAutoresizingMaskIntoConstraints = false
        webView.uiDelegate = self
        webView.navigationDelegate = self
        webView.scrollView.backgroundColor = .clear
        
        // webViewContainerは自前で用意したUIView(名前は自由に設定してください)
        webViewContainer.addSubview(webView)
        webView.topAnchor.constraint(equalTo: webViewContainer.topAnchor).isActive = true
        webView.rightAnchor.constraint(equalTo: webViewContainer.rightAnchor).isActive = true
        webView.leftAnchor.constraint(equalTo: webViewContainer.leftAnchor).isActive = true
        webView.bottomAnchor.constraint(equalTo: webViewContainer.bottomAnchor).isActive = true
        webView.heightAnchor.constraint(equalTo: webViewContainer.heightAnchor).isActive = true
    }
}


extension ViewController: WKUIDelegate {
}


extension ViewController: WKNavigationDelegate {
}

この設定で表示させられます。ここからは細かい設定について説明します。

WebViewの生成の補足

WKWebViewConfiguration

WKWebViewConfigurationはWKWebViewの初期化時に参照される設定プロパティが含まれるクラスです。

WKWebViewConfigurationを使用すると、Webページがレンダリングされるまでの時間、メディア再生の処理方法、ユーザーが選択できるアイテムの細分などのオプションを決定できます。

WKWebViewConfigurationは、WKWebViewが最初に初期化されたときにのみ使用されます。 このクラスを使用して、WKWebViewの作成後にその設定を変更することはできません。

WKUserContentController

WKUserContentControllerJavaScriptからのコールバック受信や、スクリプトをWebビューに挿入する方法を提供します。 必要であれば、WKWebViewConfiguration に生成したWKUserContentControllerを登録する処理を追加してください。

基本的な使い方の箇所で、userContentController.add(self, name: "testCallBack")のようにコールバックを登録しています。 具体的なコールバックの処理は下記のように追加します。

extension WebViewController: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if(message.name == "testCallBack") {
             print(" \(message.body)") 
        }
    }
} 

これでJavascriptからメッセージが送られてきたらアプリで受け取って処理を行うことができます。

WKUIDelegateとWKNavigationDelegate

基本的な使い方の箇所でWKUIDelegateWKNavigationDelegateの設定を行いました。

ここでは、WKUIDelegateとWKNavigationDelegateについて補足します。

まず、WKUIDelegateはWebビューのUIを制御するデリゲートです。 主にウィンドウ、Javascriptのダイアログ(alert, confirm, prompt)、コンテキストメニューを制御します。

下記に例を記載します。

extension WebViewController: WKUIDelegate {

    // Webビューを作成
    func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
        guard let url = navigationAction.request.url else {
            return nil
        }
        
        guard let targetFrame = navigationAction.targetFrame,
              targetFrame.isMainFrame else {
            // 同一ページでWebを読み込む
            webView.load(URLRequest(url: url))
            return nil
        }
        
        return nil
    }

    // Javascriptのalertダイアログを表示する
    func webView(
        _ webView: WKWebView,
        runJavaScriptAlertPanelWithMessage message: String,
        initiatedByFrame frame: WKFrameInfo,
        completionHandler: @escaping () -> Void
    ) {
        let alertController = UIAlertController(
            title: "",
            message: message,
            preferredStyle: .alert
        )
        alertController.addAction(UIAlertAction(title: "OK", style: .default) {action in
            completionHandler()
        })
        present(alertController, animated: true, completion: nil)
    }
}

他にもデリゲート処理はありますが、公式ドキュメント等を参考にしてみてください。

WKNavigationDelegateはwebビューのナビゲーションのデリゲートです。 遷移開始時・開始時・ページの読み込み完了時・読み込みエラー発生時リダイレクト時などのイベントを受け取りたい場合はWKNavigationDelegateを継承します。

下記に例を記載します。

extension WebViewController: WKNavigationDelegate {
    // リクエストを許可するか判定
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        decisionHandler(.allow)
    }
    
    // コンテンツを読み込んだ時に呼ばれるメソッド
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    }
}

この例では、特定のURLのみ許可するのに使用したり、コンテンツを読み込んだ後に何か処理を行う場合に使用します。

HTMLを表示させる

次にHTMLを表示させる方法です

HTMLファイルを置いたら、Webページと同じように、ロードするだけで表示できます。

func loadLocalHTML() {
    guard let path: String = Bundle.main.path(forResource: "index", ofType: "html") else { return }
    let localHTMLUrl = URL(fileURLWithPath: path, isDirectory: false)
    webView.loadFileURL(localHTMLUrl, allowingReadAccessTo: localHTMLUrl)
}

Bundle.main.pathはプロジェクトのBuild PhasesのCopy Bundle Resourcesにあるファイルを取得するはずです。

また、HTMLのファイルではなく、文字列を読み込む場合にはloadHTMLStringメソッドを使用します。

func loadLocalHTML() {
    let webView = WKWebView()
    webView.loadHTMLString("<html><body><p>Hello!</p></body></html>", baseURL: nil)
}

CSSの適応

CSSを適用させるにはページ読み込み完了したらCSSファイルを読み込む処理を行います

まずはHTML読み込み時にCSSも読み込むパターンです。

private func loadHTML() {
        guard let html = Bundle.main.url(forResource: "index"(ファイル名), withExtension: "html")  else { return }
        guard let htmlString = try? String(contentsOf: html) else { return }

        /// CSSのファイルURLを取得する
        guard let css = Bundle.main.url(forResource: "style"(ファイル名), withExtension: "css")  else { return }

        webView.loadHTMLString(htmlString, baseURL: css)
 }

ただ、CSSファイルを読み込むパターンはうまく適用されない場合もあります。

そこで、HTML表示後にCSSを適用させるパターンも紹介します。

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        let css = "img {max-width: 100%; width: 100%; height: 75%; vertical-align: middle;}"
        
        let js = "var style = document.createElement('style'); style.innerHTML = '\(css)'; document.head.appendChild(style);"
        
        cssWebView.evaluateJavaScript(js, completionHandler: nil)
}

WKNavigationDelegateのdidFinish(表示完了時)にCSSを含めたJavascriptを実行する方法です。 evaluateJavaScriptメソッドはJavascriptを実行するメソッドです。

また、下記のようにスクリプトを追加することで表示前にCSSを適用するパターンもあります。

let script = WKUserScript(source: "JavaScriptコード", injectionTime: .atDocumentEnd, forMainFrameOnly: true)

webView.configuration.userContentController.addUserScript(script)

let controller = WKUserContentController()
controller.addUserScript(script)
let configuration = WKWebViewConfiguration()
configuration.userContentController = controller
let webView = WKWebView(frame: view.bounds, configuration: configuration)

CSSの適用は場合によってはできない場合にもあるので注意が必要です。

Javascriptとの連携

Javascriptからアプリの処理を実行したり、アプリからJavaScriptのメソッドを実行したい場合があると思います。 それぞれの方法を紹介します。

Javascriptからアプリの処理を実行する

Javascriptでボタンのタップアクションをアプリ側で受け取りアプリで処理を行う方法について説明します。 WKUserContentControllerの説明と多少、重複します。

HTMLは次の例を使用します。

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
        <script type="text/javascript">
            function showMessage() {
                webkit.messageHandlers.showMessage.postMessage("");
            }
            function setMessage(message) {
                document.getElementById("text").innerHTML = message;
            }
        </script>
        <input type="button" value="Button"
               onClick="showMessage();" />
        <p id="text"></p>
    </body>
</html>

基本的な使い方の箇所でコールバックの設定を行いましたが、同様にハンドラを追加します。

let config: WKWebViewConfiguration = WKWebViewConfiguration()
let controller: WKUserContentController = WKUserContentController()

// JavaScriptから呼び出せるメッセージハンドラを設定する
controller.add(self, name: "showMessage")

config.userContentController = controller
webView = WKWebView(frame: self.view.bounds, configuration: config)

// 既にwebViewを初期化している場合は、こちらでもよい
webView.configuration.userContentController.add(self, name: "showMessage")

WKScriptMessageHandlerに準拠して、上で定義したメッセージハンドラをJavaScriptから呼び出すと、userContentController(_:didReceive:)が実行され、メッセージに応じた処理ができます。

extension WebViewController: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "showMessage" {
            print("\\\\(message.body)")
        }
    }
}

message.bodyはHTMLでwebkit.messageHandlers.showMessage.postMessage("");の箇所のpostMessageで設定した文字列になります。

アプリからJavaScriptのメソッドを実行する

今度は先程と逆でアプリからJavascriptを実行する方法です。

次のようにevaluateJavascriptメソッドでJavascriptの関数を呼び出します

let message = "Message"

let jsFunc = "test(\\\\"\\\\(message)\\\\")"

// JavaScriptメソッドを実行する
webView.evaluateJavaScript(jsFunc, completionHandler: { (object, error) -> Void in
    // JavaScriptメソッドの実行結果を受け取れる
})

参照

developer.apple.com

sogablog.net

gekkado.com

qiita.com

rbenvでうまくバージョンアップできない場合の確認(あまりないパターン)

今回は小ネタです。しかも、iOSの開発している人しか発生しないかもしれないです。

rbenvを使って別のバージョンのRubyをインストールしようと思ったら下記のエラーが発生しました。

$ rbenv install 3.1.2
Downloading openssl-3.0.5.tar.gz...
-> https://dqw8nmjcqpjn7.cloudfront.net/aa7d8d9bef71ad6525c55ba11e5f4397889ce49c2c9349dcea6d3e4f0b024a7a
Installing openssl-3.0.5...

BUILD FAILED (macOS 12.6.4 using ruby-build 20220721)

Inspect or clean up the working tree at /var/folders/14/zqq9_0gx5s5f78ks8s_p4k240000gn/T/ruby-build.20231209082717.65642.nfBnoE
Results logged to /var/folders/14/zqq9_0gx5s5f78ks8s_p4k240000gn/T/ruby-build.20231209082717.65642.log

Last 10 log lines:
***                                                                ***
***       perl configdata.pm --dump                                ***
***                                                                ***
***   (If you are new to OpenSSL, you might want to consult the    ***
***   'Troubleshooting' section in the INSTALL.md file first)      ***
***                                                                ***
**********************************************************************
xcrun: error: active developer path ("/Applications/Xcode.app/Contents/Developer") does not exist
Use `sudo xcode-select --switch path/to/Xcode.app` to specify the Xcode that you wish to use for command line developer tools, or use `xcode-select --install` to install the standalone command line developer tools.
See `man xcode-select` for more details.

対応方法はログに記載のあるようにXCodeのxcrunコマンドに参照問題を解消すれば大丈夫でした。 自分がインストールしていたXCodeのパスを選択することでインストールできるようになりました。

sudo xcode-select --switch path/to/Xcode.app(Xcodeのパスを指定)

まとめ

Xcodeを入れていると結構ややこしいことが起こる可能性があるので注意が必要です。

Rubyの設定がバッティングしてエラーになることもありそうです。

参照

qiita.com

zenn.dev

qiita.com

MySQLのアクセス権限エラーの解決方法

今回はMySQLで「Access denied for user ‘root’@’localhost’」というエラーが発生した場合の対処法についてです。

$ mysql -u rootを実行すると「Access denied for user ‘root’@’localhost’ (using password: NO)」というエラーが表示されました。

rootユーザーなのでコマンドが間違っていることもなく、別に原因がありそうです。

「(using password: NO)」という部分について調べてみると、以下2つの可能性がありそうです。(「using password: YES」でも下記の原因があるかもしれないので疑ってみてください)

①rootユーザーにパスワードを設定していない
②そのそもrootユーザーを作成していない

②のrootユーザーを作成していない場合にはユーザーを作成しましよう。

今回メインで説明するのは①のrootユーザーにパスワードを設定していない場合です。 パスワードを設定するためにはrootユーザーで接続する必要があります。

まず、MySQLを起動している場合には、停止します。

$ mysql.server stop

そして、権限を回避してアクセスできるように下記コマンドを実行します。

$ mysqld_safe --skip-grant-tables

上記コマンドを入力したら、新しくターミナルタブを開きます。 その状態で、rootユーザーでの接続を行うと、接続できるはずです。

$ mysql -u root

mysql>

rootに接続してパスワードを設定していなければ、下記コマンドのようにしてパスワードを変更しましょう。 パスワードはMySQLが定めるパスワードポリシーに準拠する必要があるので注意してください。

alter user root identified by 'password';

そして、権限を変更します。 ユーザーが使用できるその他の一般的なアクセス権限一覧を変更します。 具体的には次のような権限に変更できます。

権限 内容
ALL PRIVILEGES ユーザーは指定されたデータベースへフルアクセスができます。データベースが選択されていない場合は、システム全体のグローバルアクセスができます
CREATE 新しいテーブル、データベースを作成できます
DROP テーブル、データベースを削除できます
DELETE テーブルから行を削除できます
INSERT テーブルに行を挿入できます
SELECT データベースを読み取ることができます
UPDATE テーブルの行を更新できます
GRANT OPTION 他のユーザーの権限の付与または削除ができます

今回は「ALL PRIVILEGES」で変更してみます。

mysql> grant all privileges on *.* to 'root';
Query OK, 0 rows affected (0.01 sec)

成功したら、下記コマンドを実行しましょう。 直接userテーブルをメンテナンスするオペレーションする場合はFLUSH PRIVILEGESを実行して、有効化します。

mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)

もしかしたら、権限を変更する際に失敗する可能性があります。コマンドが間違っていない場合でも失敗する場合にはflush privileges;を実行してから権限変更のコマンドを実行すると成功することがあります。

ここで、exitコマンドからMySQLから抜けて、MySQL再起動に再アクセスを行うとエラーは発生しないはずです。

$ mysql.server start
Starting MySQL
.. SUCCESS! 

$ mysql -u root -p
Enter password: 

参照

qiita.com

qiita.com

qiita.com

ssabcire.hatenablog.com

qiita.com

qiita.com

Cookie/Session/キャッシュとは?Webブラウザの重要な要素を解説

今回はWebブラウザを使うときによく耳にするCookie/Session/キャッシュについて記載します。

CookieSessionキャッシュってWebでよく聞く、似たようなものじゃないの?と思うかもしれませんが、違うものなのでそれぞれ紹介していきます。

ちなみに、今回タイトルはAIに決めてもらいました。今までの自分のブログと毛色が違うタイトルになりましたが、いいタイトルになったと思います。

Session

まず、Sessionとは一体なんでしょうか?

Sessionは複数のリクエストを同一ユーザーと認識すること(機能)です。

HTTPは状態を持たないため同じブラウザでリクエストしても同じユーザーとは認識できないという特徴があります。

同じユーザーとは認識できるようにするのがSessionの役割です。

セッションとは、ウェブサーバー上で動作しているアプリケーションがユーザーの情報を一時的に保存する仕組みです。一般的に、この情報はサーバーのメモリやデータベースに保存されます。

例えば、ユーザーがウェブサイトを訪れると、セッションが開始され、セッションIDが生成されます。 IDはこの後説明するCookieとしてブラウザに保存されます。 ユーザーが再びサイトを訪れると、IDに基づいてサーバー側の情報が呼び出されます。

例えば、ChromeCookieを確認するには、以下にアクセスすることで確認できます。

Sessionで「複数のリクエストを同一ユーザーと認識する」と説明しました。

Cookieはユーザー識別を実現させるための手段なので、Sessionとは異なります。(Sessionは目的や機能を表しています)

Cookieはユーザーのブラウザに直接保存される小さなテキストファイルです。 サーバーがテキストファイルをブラウザに送信し、ブラウザは保存して次回のサーバーへのリクエスト時に同じCookieを送り返します。
Cookieはクライアント側のマシン(主にブラウザ)にのみ保存され、セッションはブラウザだけでなく、サーバーにも情報が保存されます。

キャッシュ

キャッシュについては「キャッシュを削除してください」という言葉を聞いたことがある方もいるかもしれません。

キャッシュは、よく使うデータへのアクセスを速くするために、より高速な記憶装置に一時的に保存する仕組みのことを指します。 Webサイトのキャッシュは、頻繁にアクセスされるコンテンツを保存し、その情報を呼び出すことで表示速度を高速化します。

例えば、一度アクセスしたウェブページの画像やJSやCSS などのデータを保存して、次回以降同じページにアクセスしたときに再度読み込む必要がなくなり、ページの表示速度が速くなります。

JSやCSS などのデータを更新した場合に変更が反映されない場合は、コンテンツを保存している可能性があるため、「キャッシュを削除してください」といわれることがあります。

最後に

似たような言葉ですが、明確に意味が分かれていると思います。

主に、セキュリティ面で使用すると思うので、気になる方はさらに深掘りしてみてください!

参照

ipeinc.jp

ssaits.jp

Android13のプッシュ通知について

今回はAndroid13のプッシュ通知について変更点があるので紹介していきたいと思います。

Android 14 ベータ版がリリースされる時期ですが.....見ていってください!

Android 13(targetSdk 33)から、アプリ通知を送るのにユーザーの事前許可が必要になりました。

実装方法も多少の変更がありました。

通知の権限

まずは、POST_NOTIFICATIONSの権限を追加します。 この権限を追加しないとAndroid13ではプッシュ通知の権限を確認するダイアログは表示されません。

<manifest ...>
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
    <application ...>
        ...
    </application>
</manifest>

権限確認ダイアログでは①「許可」を選択する、②「許可しない」を選択する、③どちらのボタンも押さずにダイアログをスワイプして閉じるという3パターンのアクションを取ることができます。

③の場合には権限を未選択状態になるので注意が必要です。

また、Android12以下ではダイアログは表示されないので、今まで通りチャンネルを作成して通知の設定を行なってください。

通知のタイミング

通知のタイミングについては開発者で決めることができます。

ActivityResultLauncherを起動して結果を受け取れるようにします。

private val requestPermissionLauncher =
    registerForActivityResult(ActivityResultContract.RequestPermission()) { isGrant: Boolean ->
    }

下記のようにしてダイアログを表示させます。(どこかのActivityで表示させてください)

if (ContextCompat.checkSelfPermission(
            this,
            Manifest.permission.POST_NOTIFICATIONS,
        ) != PackageManager.PERMISSION_GRANTED
    ) {
        requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
    }

ここまでの実装でプッシュ通知確認のダイアログは表示できるようになり、権限の設定は行えます。 しかし、上記の呼出処理だと、拒否し続けると画面表示のたびに確認ダイアログが表示されることになってしまいます。

PackageManager.PERMISSION_DENIEDで判定すればいいんじゃないのと思ったかもしれないですが、最初の1回目が表示されなくなる可能性が高いため別の方法が良さそうです。

そこで、shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)を使用します。

shouldShowRequestPermissionRationaleは、以前ユーザーがリクエストを許可しなかった場合trueを返します。

しかし、ここでも注意が必要なのですが、何回拒否したのかで結果が変わります。

  • 1回も拒否していない→shouldShowRequestPermissionRationale = false
  • 1回拒否→shouldShowRequestPermissionRationale = true
  • 2回以降拒否→shouldShowRequestPermissionRationale = false

このように1回拒否した時のみtrueを返します。なので、これだけだと判断できない場合には端末情報として許諾したかどうかを保持するなどの追加で必要になるかもしれません。

最後に

開発者側としては微調整が必要になりましたが、使いやすくなったと思います。

プッシュ通知はiOSのフローの方がふさわしいと思っているので近づいてよかったと思いました。

参照

developer.android.com

www.fuwamaki.com

kubiakdev.medium.com

staticについて

今回はstaticについて記載しようと思います。特に、static変数とstaticメソッドについて解説します。

static変数

static変数とは、クラスをインスタンス化せずにその変数にアクセスすることができる変数です。

static変数はクラス名.static変数のように記述しますのでクラス変数、非static変数はインスタンス名.変数のように記述しますのでインスタンス変数とも呼ばれます。

クラス変数はそのクラスから作られたインスタンスの全てで共有される変数、インスタンス変数はそのインスタンス1個内だけで使われる変数となります。

例えば、Javaでの例を下記に記載します。 まず、下記のようなクラスがあるとします。messageはstatic変数です。

public class MessageText {
    public static String message;  // static変数

    public void echoMessage () {
        System.out.println(message);
    }
}

message変数は、各インスタンスではなくMessageTextクラス上に定義されるため、jvmプロセス上にたった1つしか存在しない。

以下のmain文で実行してみると、2つのインスタンスが同じメモリ上の値を参照しているのがわかると思います。

public static void main (String[] args) {
    MessageText.message = "メッセージ1";
    MessageText message1 = new MessageText();
    MessageText message2 = new MessageText();

    message1.echoMessage(); // メッセージ1が出力される
    message2.echoMessage(); // メッセージ1が出力される

    MessageText.message = "メッセージ2";
    message1.echoMessage(); // メッセージ2が出力される
    message2.echoMessage(); // メッセージ2が出力される
}

static変数の使い所は定数クラス内でのみ使用される共用変数が考えられます。定数の場合にはfinal修飾子をつけて上書きできないようにします。 クラス内でのみ使用される共用変数はアクセス修飾子をprivateにして、そのクラス内からのみ参照・更新できないようにアクセスレベルを制限します。

今回はJavaで補足しましたが、他のプログラミング言語でもstaticの概念があればあまり差はないと思います。

staticメソッド

次はstaticメソッドについてです。

staticメソッドはクラスに直接属し、クラスが実行するメソッドです。 インスタンスに属し、インスタンスが実行するインスタンスメソッドとは異なります。

staticメソッドを呼び出すには、原則としてクラス名.staticメソッド名と指定します。

staticメソッドの特徴は下記3点です。

インスタンスを生成せずに呼び出せます。頻繁に呼び出されるユーティリティメソッドはよくstaticメソッドとして実装されます。

インスタンスを複数生成しても、そのインスタンスが持つstaticメソッドの処理はどのインスタンスも同じ動作になります。 なので、SwiftでのExtensionに使われたりします。

インスタンス化しないと状態を持てないので、状態に依存しないことを保証できます。 具体的には、 与えられた引数のみでメソッドの挙動が決まることが保証できます。

staticメソッドにする場合には副作用のない、純数な関数であること。 つまり、関数の出力値が入力値によってのみ決まる関数にした方がよさそうです。

注意点としてはstaticメソッドではstatic変数しか扱えないです。

public class Test {
    String name;
    Static int age;

    public static void setAge() {
        Test.age = 20;
        System.out.println(this.name + "です");
    }
}

上記の例では、this.nameが参照できないのでエラーになります。

終わりに

static変数とstaticメソッドについて解説しました。

static変数は定数として使用できるかと思いますが、staticメソッドはオブジェクト指向から外れる処理になる気がするので、 共通処理で使用するくらいかなと思います。

参照

www.sejuku.net

www.sejuku.net

www.bold.ne.jp

RailsでのDBあれこれ

今回はRailsでDBのテーブル追加や更新などの手順をまとめようと思います。

テーブル追加

まずはテーブルを追加する際の手順です。

基本的には下記のようにして実行します。

$ rails model モデル名 属性名1: データ型 属性名2: データ型

属性とデータ型はカラムとカラムのデータ型の定義です。

カラムが多い場合にはモデル名だけ設定しても大丈夫です。その場合にはtimestamps(create_atとupdate_at)のみ作成されます。

例えば、カテゴリーテーブルを作成する場合には次のようなコマンドを実行します。

$ rails g model category

成功すると次のように作成されたファイルの一覧が表示されるはずです。

invoke  active_record
      create    db/migrate/20230819085954_create_categories.rb
      create    app/models/category.rb
      invoke    test_unit
      create      test/models/category_test.rb
      create      test/fixtures/categories.yml

「db/migrate/20230819085954_create_categories.rb」はマイグレーションファイルです。
「category.rb」はモデルのクラスファイルです。
「category_test.rb」はモデルのテストファイルです。
「categories.yml」はモデルのテストデータを作成する際に使用するfixtureファイルです。

マイグレーションファイルに次のようなカラムを追加します。

class CreateCategories < ActiveRecord::Migration[7.0]
  def change
    create_table :categories do |t|
      t.integer :category_id, null: false
      t.string :name, null: false
      t.timestamps
    end
  end
end

Railsではidというカラムがあり、主キーのような一意の値を持ちます。 idではなく、独自のカラムを主キーにしたい場合には次のようにします。

class CreateCategories < ActiveRecord::Migration[7.0]
  def change
    create_table :categories, id: false do |t|
      t.integer :category_id, null: false, primary_key: true
      t.string :name, null: false
      t.timestamps
    end
  end
end

id: falseを設定して、主キーにしたいカラムにprimary_key: trueを追加します。

初期データ追加

初期データを使用したい場合にはseedファイルを編集して、初期データを投入します。

Railsプロジェクトを作成したときにdb/seeds.rbが作成されているはずですので、seeds.rbに初期データを記載します。

初期データを設定した上で、モデルを作成します。下記のようなイメージです。

User.create([
  {name: "AAA"},
  {name: "BBB"},
  {name: "CCC"},
  ])

seedファイルを編集した後は、rails db:seedコマンドを実行してDBに反映させます。

テーブル削除

まずは、マイグレーションファイルを作成します。 Userテーブルを削除するとします。

$ rails g migration Drop Users

コマンドを実行すると、db/migrate配下にマイグレーションファイルが作成されていれば成功です。

次にマイグレーションファイルを編集します。

class DropUsers < ActiveRecord::Migration[7.0]
  def change
    drop_table :goal_images do |t|
      t.integer :name

      t.timestamps
    end
  end
end

上記のように、drop_tableメソッドを使用します。ブロック内にはもともとあったカラムを記述します。

ファイルを編集したら下記コマンドでマイグレーションを実施します。

$ rails db:migrate

ここで注意しなければいけないことはテーブルは削除できても、モデルは別途削除する必要があります。 逆にモデルを削除するだけだとテーブルは削除できていないので、気をつけないといけません。

ロールバック

DBを1つ前の状態に戻すことができます。 次のコマンドで行います。

$ rails db:rollback

これにより、migrateする前の状態に戻ることができました。

最後に

今後追加していくと思います!!!

参照

zenn.dev

toshpit.com

k-koh.hatenablog.com

vdeep.net

zenn.dev

qiita.com