TataraというRubyで型を使えるライブラリを作ってみた

はじめに

この記事は最近僕が作っているRubyで型を使えるライブラリの紹介記事です。

対象読者としては、Rubyで簡単に型のようなものを使ってみたい人、またはC++でのRuby拡張を作ってみたい人を想定しています。

Tataraの紹介をしつつ、簡単なC++でのRuby拡張実装についても触れていきます。

作ったもの

TataraというRubyで簡単な型を使えるようにするRubyExtensionを作りました。RubyGemsにもリリースしており、v0.2.0が現在リリースされている最新バージョンになります。

v0.2.0まではRiceというRubyExtensionを簡単にC++で使用していました。しかし、GCへの管理追加が少しややこしかったり、RiceでC++のコードをRuby向けにラップしているため処理が遅くなったりするという問題がありました。そこで次期リリースではC++のみでの実装に書き直し、GCへの管理追加や処理速度向上を図っています。

ちなみに、次にリリースするv0.3.0は11月中にリリースする予定で実装を進めています。

サンプルコード

例えば、Tataraを使うことで以下のようなコードを実行できます。

require 'tatara'

@int_container = Tatara::IntVector.new

(1..10).each{|i| @int_container << i}
# Insert 1..10 to @int_container

@int_container.map{|i|
    puts i
    # 1..10 value is shown
}

@int_containerは整数値のみが保存できるVectorになります。浮動小数点数値なども代入できますが、整数値に暗黙的に変換されます。

@int_container = Tatara::IntVector.new
@int_container << 4.2
# => Push 4

ちなみに、文字列を保存しようとするとエラーになります。

@int_container = Tatara::IntVector.new
@int_container << "42"
# => Error!

このようにTataraでは簡単な型が使えるようにしてあります。

作ろうと思ったきっかけ

Ruby自体は非常に書きやすく、楽しくコーディングができる素晴らしい言語です。

ですが、元々静的型付け言語(C/C++など)を触ることが多かったため「Rubyにも型のようなものが欲しいなぁ……」と思うこともありました。

そんな折、RiceextppなどでRubyExtensionを作れそうだとわかり、「面白そうなので作ってみよう !」となり作ってみました。

やったこと

開発初期のころはRiceでのRubyExtension作成についてドキュメントや記事などを漁りに漁っていました。で、Riceで実装できそうなことがわかったので少しづつ実装を進めていった感じですね。

最初はIntegerやFloatなどを実装し、その後VectorやMapなどを実装していきました。

v0.1.0をリリースした辺りでGCへの管理追加などがややこしいことが判明し、Riceを外す方法を模索し始めました。extppなどでのRubyExtension実装ついても調べたりしました。

結果として、Ruby自体の実装コードなどを読みつつ、以下の記事を参考にRiceを外していくことにしました。そのおかげでRubyの実装なども読むことができたので良い経験が積めたと思います。

Riceでの実装

Riceでは以下のようにC++で作成したクラスをラップしてRuby向けに使用できるようにできます。

#include <rice/Data_Type.hpp>
#include <rice/Constructor.hpp>

using namespace Rice;

class Integer {
    int value = 0;

    public:
        Integer();
        ~Integer();
        int assignment(const int var);
        int return_value();
};

Integer::Integer() {}

Integer::~Integer(){}

int Integer::assignment(const int var) {
    return this->value = var;
}

int Integer::return_value() {
    return this->value;
}

extern "C" {
    void Init_tatara() {
        Module rb_mTatara = define_module("Tatara");

        Data_Type<Integer> rbcInteger = define_class_under<Integer>(rb_mTatara, "Integer")
            .define_constructor(Constructor<Integer>())
            .define_method("value", &Integer::return_value)
            .define_method("value=", &Integer::assignment);
    }
}

RiceではC++で定義したクラスをData_TypeRubyのクラスへとラップしてくれます。またモジュール名などはModule rb_mTatara = define_module("Tatara");のように作成することができます。

Module rb_mTatara = define_module("Tatara");

Data_Type<Integer> rbcInteger = define_class_under<Integer>(rb_mTatara, "Integer")

上記のコードではTataraモジュール内にIntegerクラスを作成しています。

.define_constructor(Constructor<Integer>())

またこのコードではC++Integerクラスのコンストラクタをnewとして利用しています。

あとは、以下のように必要なメソッドをdefine_methodでクラスに追加しています。

.define_method("value", &Integer::return_value)
.define_method("value=", &Integer::assignment);

define_methodはメソッド名と実行するメソッドを引数として渡すことで、C++側で定義したメソッドをRuby側で使用することができます。

これにより、以下のようなRubyのコードが実行できます。

@integer = Tatara::Integer.new
@integer.value = 42
puts @integer.value
# => 42

このようにRiceを使うことでC++で定義したクラスをRubyで使用することができます。

ちなみに、テンプレートなどを使う場合は以下のように型を指定する必要があります。

Data_Type<CppArray<int>> rb_cIntArray = define_class_under<CppArray<int>>(rb_mTatara, "IntArray")
    .define_constructor(Constructor<CppArray<int>>());

C++への実装書き直し

先ほどRiceで書いていたコードは以下のように書き直すことができます。

#include <ruby.h>

class Integer {
    int value = 0;

    public:
        Integer();
        ~Integer();
        int assignment(const int var);
        int return_value();
};

Integer::Integer() {}

Integer::~Integer(){}

int Integer::assignment(const int var) {
    return this->value = var;
}

int Integer::return_value() {
    return this->value;
}

static Integer *getInteger(VALUE self) {
    Integer *ptr;
    Data_Get_Struct(self, Integer, ptr);
    return ptr;
}

static void wrap_int_free(Integer *ptr) {
    ptr->~Integer();
    ruby_xfree(ptr);
}

static VALUE wrap_int_alloc(VALUE klass) {
    void *p = ruby_xmalloc(sizeof(Integer));
    p = new Integer;
    return Data_Wrap_Struct(klass, NULL, wrap_int_free, p);
}

static VALUE wrap_int_init(VALUE self) {
    Integer *p = getInteger(self);
    p = new Integer;
    return Qnil;
}


static VALUE wrap_int_return_value(VALUE self) {
    const int value = getInteger(self)->return_value();
    VALUE result = INT2NUM(value);
    return result;
}

static VALUE wrap_int_assignment(VALUE self, VALUE value) {
    const int v = NUM2INT(value);
    const int r = getInteger(self)->assignment(v);
    VALUE result = INT2NUM(r);
    return result;
}

extern "C" {
    void Init_tatara() {
        VALUE mTatara = rb_define_module("Tatara");

        VALUE rb_cInteger = rb_define_class_under(mTatara, "Integer", rb_cObject);

        rb_define_alloc_func(rb_cInteger, wrap_int_alloc);
        rb_define_private_method(rb_cInteger, "initialize", RUBY_METHOD_FUNC(wrap_int_init), 0);
        rb_define_method(rb_cInteger, "value", RUBY_METHOD_FUNC(wrap_int_return_value), 0);
        rb_define_method(rb_cInteger, "value=", RUBY_METHOD_FUNC(wrap_int_assignment), 1);
    }
}

C++のクラスをそのままRuby側で呼び出すことはできないので、以下のような関数を作成し、Ruby側から呼び出せるようにしています。

static Integer *getInteger(VALUE self) {
    Integer *ptr;
    Data_Get_Struct(self, Integer, ptr);
    return ptr;
}

static void wrap_int_free(Integer *ptr) {
    ptr->~Integer();
    ruby_xfree(ptr);
}

static VALUE wrap_int_alloc(VALUE klass) {
    void *p = ruby_xmalloc(sizeof(Integer));
    p = new Integer;
    return Data_Wrap_Struct(klass, NULL, wrap_int_free, p);
}

static VALUE wrap_int_init(VALUE self) {
    Integer *p = getInteger(self);
    p = new Integer;
    return Qnil;
}

モジュールはrb_define_moduleで作成でき、以下のようにTataraというモジュールを作成しています。

VALUE mTatara = rb_define_module("Tatara");

また、以下のコードではIntegerクラスをTataraモジュール内に作成し、その基底クラスにObjectクラスを使用しています。

VALUE rb_cInteger = rb_define_class_under(mTatara, "Integer", rb_cObject);

Rubyソースコード内ではVALUE型を使うことでインスタンスの値などを取得することができています。

getIntegrではインスタンス自身からC++のクラスのポインタを取得し、そのポインタを使ってクラスで定義されているメソッドを実行しています。 例えば以下のようにreturn_valueメソッドを呼び出すことができます。

static VALUE wrap_int_return_value(VALUE self) {
    const int value = getInteger(self)->return_value();
    VALUE result = INT2NUM(value);
    return result;
}

またwrap_int_freeではRubyGCでメモリが解放される際の処理を実装しています。

static void wrap_int_free(Integer *ptr) {
    ptr->~Integer();
    ruby_xfree(ptr);
}

wrap_int_allocRubyのコードで新しくnewインスタンスを作成した際のアロケーションを実装しています。

static VALUE wrap_int_alloc(VALUE klass) {
    void *p = ruby_xmalloc(sizeof(Integer));
    p = new Integer;
    return Data_Wrap_Struct(klass, NULL, wrap_int_free, p);
}

rb_define_alloc_funcアロケーション時に実行する関数を指定できます。

rb_define_alloc_func(rb_cInteger, wrap_int_alloc);

wrap_int_initinitializeを実装しています。

static VALUE wrap_int_init(VALUE self) {
    Integer *p = getInteger(self);
    p = new Integer;
    return Qnil;
}

最後に、以下のようにC++で定義されたクラスのメソッドを実行するラップ関数を作成します。

static VALUE wrap_int_return_value(VALUE self) {
    const int value = getInteger(self)->return_value();
    VALUE result = INT2NUM(value);
    return result;
}

static VALUE wrap_int_assignment(VALUE self, VALUE value) {
    const int v = NUM2INT(value);
    const int r = getInteger(self)->assignment(v);
    VALUE result = INT2NUM(r);
    return result;
}

INT2NUM()NUM2INT()はCとRubyとの間でデータの変換を行う処理になります。この処理を間に挟むことでRuby内の変数の値などをC++側に渡すことができます。

あとは、作成したラップ関数を以下のようにメソッドとして追加します。

rb_define_method(rb_cInteger, "value", RUBY_METHOD_FUNC(wrap_int_return_value), 0);
rb_define_method(rb_cInteger, "value=", RUBY_METHOD_FUNC(wrap_int_assignment), 1);

rb_define_methodは第一引数にメソッドを追加するクラスのVALUEを渡します。第二引数にはメソッド名、第三引数には実行する関数を渡します。
RUBY_METHOD_FUNCでは渡している関数ポインタをRubyのメソッドとしてキャストしています。
第四引数は、そのメソッドが受け取る引数の数になります。0の場合は引数はなく、1などの場合はその数だけ引数を受け取ります。なお、-1の場合は可変長引数として受け取っているようです(RubyのArrayクラスの実装などで見る限り)。

このようにしてC++Rubyのクラスを作成することができます!

おわりに

今回作成したTataraの紹介をしつつ、C++でのRuby拡張の実装などについても触れてみました。普段使っているRubyの内部コードについても少し触れましたので、興味が湧いた方もおられるかもしれません。

ここから宣伝。

なお、東京近隣の方であればRuby Hacking Challenge HolidayというRubyの実装などをRubyコミッターから直々に聴けるというイベントがあります!
ちなみに、次回は「Ruby 2.7新機能紹介」や「Rails Girls Tokyo, More! 参加者の皆様と合同LT大会」を行うようです。

Ruby Hack Challenge Holiday #9 Ruby 2.7 + 年末LT大会

この記事を読んでRubyの内部に興味を持った方は是非参加してみるといいでしょう。

ちなみに、島根県浜田市ではRuby Hacking Challenge in Hamada.rbをHamada.rb内で開催しています。
僕のほうで内部のコードなど解説を行いますので、興味のある方は是非ご参加ください(なお、わかる範囲になりそうですが……)

Ruby Hacking Challenge in Hamada.rb

宣伝終わり

今後もゆっくりとTataraの実装を行いつつ、Ruby自体の実装への理解を深めていきたいと思います。

参考資料

Rubyソースコード完全解説

Rubyの拡張ライブラリの作り方

ko1/rubyhackchallenge

ruby/ruby

Mastodon v3.0.0rc1にアップデートした際のアレコレ

はじめに

Mastodon v3.0.0rc1がBump up されたので早速追従した際の出くわしたエラーの話

 

対象読者としては、さくらのスタートアップスクリプトMastodon鯖を構築した人らになるかな?

 

出くわしたもの

Mastodon v3.0.0rc1がBump upされたとのことでいつものようにmaster追従しようとしたところ

 

Error: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found

 

マイグレーション実行時に上記のエラーに出くわした。

 

調べてみるとMastodonで使用しているcharlock_holmesのバージョンが上がったっぽく、その影響で使用しているGCCのバージョンが変わったことが原因の模様

 

github.com

 

以前もcld3関連で同様のエラーに遭遇しており、「どうしたもんかなぁ……」と考えていた

 

そんな中、以下の記事を見つけ、最新のGCCをインストールすることでget kotonakiとなった

 

qiita.com

 

なので、もし同様のエラーで苦しんでいる鯖缶がいたら上記の記事を参考にGCCをインストールするとよいかもしれない

 

でも、以前のcld3の時のエラーも同様の解決方法したんだけどうまくいかなかったんだよね……。なんか手順間違えてたのやら……

C++のWebフレームワークっぽいものを作ってみた

作ったもの

github.com

 

実際のデモ

gamelinks007.net

 

やったこと

CLI11というコマンド入力をよしなにしてくれるライブラリを使って、sol new などの必要ようなコマンドを作成

https://github.com/CLIUtils/CLI11

 

入力されたコマンドをパースさせて、作成したテンプレートをC++17で追加されたfilesystem使ってコピーできるようにした

 

sol server や sol buildなどの開発用のコマンドなども追加したね

 

並行して、最低限度動くサンプルをcpp-httplib/Stimulus/Svelteで作成したり

 

今後

ORマッパーとかないのでよさげなライブラリを探しつつ、実装していこうかなぁって感じ

 

 

 

Creatodon2周年に際して思ったことアレコレ

はじめに

この記事は、Mastodon Golden Week Calendar の7日目の記事です

 

これまでとこれからの記事は下記URLより読むことができます

 

https://gw-advent.9wick.com/calendars/16

 

ちなみに、去年書いた記事はこちら

 

gamelinks007.hatenablog.com

 

今日書くこと

 

個人的にこの二年間で思ったことを徒然と書くだけです

 

Creatodonの2周年

 

えー、なんやかんやでCreatodonも無事2周年を迎えることができましたー

 

これも日頃の鯖缶の皆さんの知見であったり、ユーザーの皆様のおかげだと思います

 

この場を借りて感謝申し上げます

 

で、思ったこととか適当に

最近思ったこと

この二年でMastodonをめぐる環境ってかなり変わってきたよなーとか思いますねー

 

二コフレが鯖を閉鎖したり、JP鯖の管理者がぬるかるさんからきぼうソフトに移行したりと

 

でも、一ユーザーからしたらそんなことは大きな影響ではないんだよね

実際、ユーザー視点から言えば、他なサーバが止まったからといって何かがあるわけでもないしね(まあ、そのサーバ使ってるユーザーからすれば大問題だろうけども

 

ユーザーからすればサービスが安定して運営されてて、楽しければすべてOKなんだよね

 

だから、鯖缶がアレコレMastodonやFediverse全体のこととか話してても大して興味はないんじゃないかな?

 

というよりも、むしろ聞きたくないんじゃないかなぁと

テーマインスタンスとかだと、そのテーマについて話しがしたいのであって、Fediverseの話がしたいわけでもないしねー

 

まあ、そう言いつつ僕もアレコレ話をしているので矛盾してる感はあるんだけれどもねー

 

Creatodonの運営のアレコレ

他なテーマインスタンスとかどうされてるのか気になるところですが、Creatodonは基本的に放置してます

 

まあ、自主性を重んじるというか、単に管理するのが面倒くさいというか……

 

一番は面倒くささと「管理されるの嫌だよなぁ……」という個人的な思いでしょうかね

 

こう話さねばならないとか七面倒だし、別に自由に呟けばいいんじゃないんですかね?とかは思う

なので、特にジャンルの規定とかはしないし、好きなように使っていただければとは思う

 

とはいえ、サーバ運営に影響する内容とかだと処置しなきゃいけないし

 

その辺の尺度は、まあユーザーさんの自主性に任せてます

 

後、別にCreatodon以外にもサーバはあるので合わないと感じたら別なサーバに引っ越ししてもいいと思うんですよねー

 

「最近、あんまり浮上してないなー」とかを気にしないでもらいたいですし、合わないと感じてもTwitter見たく他に行き場所がないとかもないわけですし

 

好きなところに行ける自由さはFediverseにあるので、その自由を行使すればいいんじゃないかな

 

Fediverse全体に関しての雑感

個人的なところ、Fediverse全体は極端な人から柔軟な人まで幅広くいるカオスだと思ってる

 

で、「お一人鯖最高」の民もいれば、「LTL最高」の民もいる

その雑多なカオスな感じが好みではあるので、その状態を維持できれば面白そうだよなーとは思う

 

とはいえ、鯖缶の視点から行くと問題行為を働いたユーザーの話とかを呟いちゃったりするので完全なカオスとは言い難くなるかもだけれどね

 

あと時たまローカルルール的に「ブロックは負け」とかいう話も出ているけども別にいいんでない?

合わない人と何を話しても合うことはない時ってあるし、相手のためにも先にブロックしとけばいいんでない?

 

 

その辺も自由でいいんじゃないかと思ってる

 

今のFediverseは自由をうたってはいるけれど、一方ではローカルルール的なものを強いていたりとある種のユートピアさとディストピアさを重ねもった世界観かなぁと思う

 

ブレードランナーとかの感じに近いものを感じるので、個人的にはそういう感じも好きだけどねー

 

その他

今後、MastodonなどのAPなSNSが流行することがあるかはわからないけど、あったとしてもそれは大したことではないと思う

 

ユーザーからすれば今までのTwitter見たくあれこれ知り合いと話しやすくなるだけで、管理人からすればユーザー数が増えるだけ

 

それに今はPleromaやMisskeyなどMastodon以外にもAPでつながることのできるSNSも存在している

 

だから、Mastodonだけにユーザーが一極集中するわけでもないし、あんまし気にしなくても良いんじゃないかなと思ってる

 

まあ、それは少人数のMastodonサーバだからの話で、大所帯のところとかは大変だろうけどねー

 

あと、Twitterがつぶれるとかなっても新しい他なSNSに移行するだろうしねー

 

おわりに

まあ、何となくな感じでアレコレ書いて支離滅裂な感じもありますが、思ったことは大体書いたのでこれでいいかな

 

まあ、今後もゆるく気張らずにCreatodonの運営をやっていこうと思います

 

まあ、ハーモニーのトゥアレグ族のセリフにあるように「ほどほど」な感じでやっていこうと思いますー

 

 

Ruby Hack Challenge Holiday #2 に参加してきた!

はじめに

たまたま東京に行っているときだったので、Ruby Hack Challenge Holiday というRuby自体のコードをHackしてみようというイベントに参加してきた!

 

connpass.com

 

Ruby Hack Challenge Holidayって?

 

Rubyのコミッター(Ruby自体の開発をされている人のこと)である笹田さんと遠藤さんのお二人がサポートについているという超豪華なイベントなのよね

 

以前、Twitterでこのイベントの記事をみたこともあって前から機会があれば参加したかったのよね

 

techlife.cookpad.com

 

next.rikunabi.com

 

やってたこと

 

で、実際に参加してみた感じはいわゆるもくもく会に近いかな?

今日やってみたいこととしてRubyをHackしてみるという感じ

 

 

ちなみに僕はrubyhackchallengeという演習があるのでそれをやってた

 

github.com

 

使用している環境がWindowsだったのでHyper-VとDocker使ってコンテナ内でHackしてた(ちなみに、Dockerコンテナ使えばWindowsでもHackする環境ができるので、その辺の記事はまたQiitaかブログに上げとく予定)

 

予定の関係もあって一時間くらいしか参加できなかったんだけど、かなり濃ゆい経験ができたー

 

Hack自体としてはRubyのArrayクラスにsecondってメソッドを実装したりしてた

 

あとRubyKaigi2019のタイムテーブルが前日に発表されていることもあってRubyKaigi2019のタイムテーブル解説を聞けた!

 

rubykaigi.org

 

参加してみて

いやぁ、機会があればぜひ次も参加したい!普段使っているRubyの内部のコードをコミッターの人に質問出来たりするんだもの

 

今回は演習進めてる感じだったので、次回以降はぜひRubyの新メソッド実装とかやってみたいね!

 

それと、引き続き演習のほうも継続してやっていこうと思うね!

 

C++/Stimulus/Vue.jsでのSPAなリアルタイムチャットを作った話

はじめに

以前、QiitaにC++/StimulusでのWeb開発をまとめた以下の記事を書いた

 

qiita.com

 

qiita.com

 

それからもC++/Stimulusでアレコレ試していて、ふと「Vue.jsとStimulusって併用できるんだろうか?」と思ったので試してみたときの備忘録をまとめてみた

 

作ったもの

github.com

 

 やったこと

 

ソースとコミットを見ていただければわかりますが、基本的にはC++側でHTMLやJavaScriptをブラウザで表示できるように処理しています

 

github.com

 

そこへプラスアルファでチャット用APIを作成しているくらいです

 

フロントエンドはVue.jsで実装を行っています

vue-routerを使い、SPAとして利用できるようにしています

 

github.com

github.com

 

あとはVueコンポーネントでdata-controller="chat"を追加して、Stimulusを使用しているくらいですね

 

github.com

 

やってみて

普通にStimulusとVue.jsが併用できたなぁと……

 

HTMLにdata-controllerを追加することでStimulusは使えるので、Reactとかの仮想DOMでも同じようにStimulusが併用できるんじゃないかと思いますね

 

おそらく、Rails/Vue.jsとかにもStimulusが併用できそう

 

今後

ひとまずは、Rails/Vue.js/Stimulusでのリアルタイムチャット&SPAアプリでも作ってみようかなと思います

 

あと、C++/Stimulus/Vue.js/FireBaseでの爆速リアルタイムチャットアプリとか作ってみたいですねー

 

C++/Stimulusでのリアルタイムチャットを作った話

はじめに

以前、C++/StimulusでのWeb開発記事を書いた

 

gamelinks007.hatenablog.com

 

あれから、C++/Stimulus/FireBaseを使ったリアルタイムチャットアプリを作ったりしてた

 

github.com

 

ただ、この場合だとFireBaseに依存した実装となっているので、FireBaseが使えなくなる状況が発生すると活用が難しい

 

そこで、C++/Stimulusだけでリアルタイムチャットアプリが作れないか試してみたのが今回の話です

 

作ったもの

以下は実際に作ったものです!

 

github.com

 

やったこと

まず、C++側でチャットのメッセージを受け取ったり、表示するAPIっぽいものを作成します

 

svr.Get("/messages", [&](const httplib::Request& req, httplib::Response& res) {

std::ostringstream chat;

for(auto&& c : chats)
chat << "<p>" << c.c_str() << "</p>";

res.set_content(chat.str(), "text/plain");
});

svr.Post("/messages", [&](const httplib::Request& req, httplib::Response& res) {

chats.emplace_back(std::move(req.body));

res.set_content(std::to_string(req.get_param_value_count("chat")), "text/html");
});
 

 

肝としては、/messagesにGETリクエストが送られると最新のチャット内容すべてをtext/plainとして吐き出させているところですね

 

次に、フロントを以下のように書いていきます

 

<!DOCTYPE html>
<html>
<body>
<div data-controller="chat" data-chat-url="/messages" data-chat-refresh-interval="10">
<div data-target="chat.response"></div>
<div data-target="chat.chats"></div>
<input data-target="chat.content">
<button data-action="click->chat#chat">add</button>
</div>

<script src="./index.js" type="text/javascript"></script>
</body>
</html>

 

StimulusではJavaScript側に渡したいデータなどをdata-chat-urやdata-chat-refresh-intervallのように定義することで、以下のように渡すことができます

 

this.data.get("refreshInterval")
this.data.get("url")

 

 その為、各ページごとにaxiosやfetchでリクエスト先をガリガリと書く必要はなく、以下のようにシンプルに書くことができます

chat() {
  axios.post(this.data.get("url"), `${this.contentTarget.value}`).then*1 {
this.startRefreshing()
}
}

load() {
axios.get(this.data.get("url")).then*2
}

stopRefreshing() {
if (this.refreshTimer) {
clearInterval(this.refreshTimer)
}
}
}

肝としてはdata-chat-refresh-intervalで渡している時間が経過すると、/messagesにチャット内容をGETするように処理しているところですね

 

これで然もリアルタイムでチャットが更新されているかのように動きます!

 

おわりに

今後は、もうちょっと頑張って少し小さめな掲示板アプリでもC++/Stimulusで作ってみたいと思うねー

 

参考

stimulusjs.org

 

github.com

*1:res) => {

   console.log(res);
  }, (error) => {
console.log(error);
})
} 

 

これを利用して、以下のようにchat_controller.jsを書いていきます

import { Controller } from "stimulus";
import axios from "axios";

export default class extends Controller {
static get targets() {
return ["chats", "content", "response"];
}

connect() {
this.load();

if (this.data.has("refreshInterval"

*2:res) => {

this.responseTarget.innerHTML = res.data;
}, (error) => {
console.log(error);
})
}

chat() {
axios.post(this.data.get("url"), `${this.contentTarget.value}`).then((res) => {
console.log(res);
}, (error) => {
console.log(error);
})
}

startRefreshing() {
this.refreshTimer = setInterval(() => {
this.load()
}, this.data.get("refreshInterval"