Hello Me
Holochainチュートリアルシリーズへお帰りなさい!
これまでに作成したアプリは定数値を返していますが、より複雑なアプリケーションの場合は、データを保存できると便利ですよね。
今回は、読者のZomeにエントリタイプを追加することでデータをどのように保存するかを学びます。エントリとは、検証済みのソースチェーン内のデータを指します。
子のチュートリアルでは以下のものを追加します。
- ユーザー情報を保存する
person
という名のエントリタイプを追加します。 - UIにパブリック関数
create_person
を公開して、personエントリを作成および保存できるようにします。 - personエントリを取得するために、UIに対してパブリック関数
retrieve_person
を公開します。 - 最後に、これらの関数とやり取りができるようなUIコンポーネントを追加します。
このチュートリアルは、前回のチュートリアルに基づいて進めていくため、前回のチュートリアルを終えていない場合はまずそこから始めましょう。
まずはテストから
アプリの動作を簡単に確認できるように、テストを作成することからまず始めます。
cc_tuts/test/index.js
をまず開きましょう。
現時点では、Hello Testチュートリアルで作ったテストシナリオのままになっています。
diorama.registerScenario(“Test hello holo”, async (s, t, { alice }) => {
const result = await alice.call(“hello”, “hello_holo”, {});
t.ok(result.Ok);
t.deepEqual(result, { Ok: ‘Hello Holo’ })
// <—- ここに新たなテストを追加しましょう
新しく作るテストシナリオは、t.deepEqual(result, { Ok: 'Hello Holo' })
の真下に作ります。
下記のテストでは、「Alice」という名前のエントリを作成し、その同じエントリを取得して、名前が「Alice」であることを確認します。
Aliceという名前でcreate_person
関数への呼び出しを追加しましょう。
const create_result = await alice.call(“hello”, “create_person”, {“person”: { “name” : “Alice”}});
次の行で、関数を呼び出した際の返り値がOkであることを確認します。
t.ok(create_result .Ok);
次の行で、最後の関数の呼び出しから返された値(アドレス)を使用して、retrieve_person
関数を呼び出しましょう。
const retrieve_result = await alice.call(“hello”, “retrieve_person”, {“address”: create_result .Ok});
次の行で、この呼び出しの返り値OKであることを確認します。
次の行が、実際にテスト終了時に望む返り値で、アドレスのエントリが実際にAlice
という名前であることを確認します。
テストを実行しよう
これで、テストのコードが次のようになるはずです。
もちろん、これらのテストは失敗します。最初のエラーがどうなるか推測してみてください。
さあ、実際にどのようなエラーなのかみてみましょう。
nix-shellをまだ開いていない場合はnix-shellにまず入ります。
テストを実行してみてください。
nix-shell
で実行hc test
"Holochain Instance Error: Zome function 'create_person' not found in Zome 'hello'"
注:テストに必要な関数をまだ作成していないので、このテストは実際に行き詰まるかもしれないことに注意してください。行き詰った場合は、 ctrl-cを押して、テストを終了しましょう。
エントリを追加
zomes/hello/code/src/lib.rs
ファイルを開いてください。
ローカルのソースチェーンにエントリを追加するには、Holochainフレームワークにどのようなエントリが存在するのかを伝えることから始めます。
最初に、データの形状を定義するRustのstruct(構造体)を作成します。
Person
というstruct(構造体)は、ここに配置します。
// <—- ここにperson structを追加しましょう.
#[zome]
mod hello_zome {
次の行を追加しましょう。
このPerson struct(構造体)をJSONへと簡単に変換できるようにします。
#[derive(Serialize, Deserialize, Debug, DefaultJson, Clone)]
そして、Personという名でstructを宣言しましょう。
pub struct Person {
この、Person structにkey: valueスタイルでnameを加えます。
name: String,
}
次に、hello_zome
mod内で次の行を探してください。
#[zome]
mod hello_zome {
/* — 省略– */
#[zome_fn(“hc_public”)]
fn hello_holo() -> ZomeApiResult<String> {
Ok(“Hello Holo”.into())
}
// <—- ここに次のコードを追加しましょう。
次に、person_entry_def
という関数を追加します。この関数は、Holochainに「personというエントリタイプがありますよ」と伝える関数です。
#[entry_def]
fn person_entry_def() -> ValidatingEntryType {
ValidatingEntryType
を簡単に作成できるentry!
マクロを次に追加してください。
entry!(
一貫性を保つために、Person
struct(構造体)と同じ名前を付けましょう。通常、エントリタイプのnameは小文字になります。
エントリの名前と一緒にそのエントリについての簡単な説明も追加しましょう。
name: “person”,
description: “Person to say hello to”,
このタイプのエントリはこのエントリの所有者であるエージェント(ユーザー)のためだけのものであるため、エントリの共有タイプをプライベートに設定します。
sharing: Sharing::Private,
このエントリを検証するために必要な情報を指定するvalidation_package
関数を次に追加します。
validation_package: || {
hdk::ValidationPackageDefinition::Entry
},
このエントリを検証するvalidation
関数を次に追加します。
Person
struct(構造体)の形状に適合する限り、このエントリは常にOkであることを返します。
validation: | _validation_data: hdk::EntryValidationData<Person>| {
Ok(())
}
)
}
これで、実際のPerson
エントリを作成し、ソースチェーンに保存できるようになりました!
検証ルールに関する注意:検証ルールは非常に重要なものです。これは、Holochainアプリの言わば「ゲームのルール」です。現在は、単にOk(())
を返していますが、これでもデータ型がString
を含むname
プロパティを持つPerson
struct(構造体)であることを検証していることは理解するべきです。基本的に、このvalidation関数では、PersonというエントリはこのPerson structに基づいた形式でなければならないということを示しています。
Useステートメントを付け足しましょう
上記のコードでは、まだuseステートメントで定義されていないいくつかのタイプとマクロを使用しました。これにより、Rustサーバはこれらを見つけることができていません。
なので、次のuse
ステートメントを追加しましょう。
#![feature(proc_macro_hygiene)]
+#[macro_use]
extern crate hdk;
extern crate hdk_proc_macros;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
+#[macro_use]
extern crate holochain_json_derive;
use hdk::{
+ entry_definition::ValidatingEntryType,
error::ZomeApiResult,
};
+use hdk::holochain_core_types::{
+ entry::Entry,
+ dna::entry_types::Sharing,
+};
+
+use hdk::holochain_json_api::{
+ json::JsonString,
+ error::JsonError,
+};
+
+ use hdk::holochain_persistence_api::{
+ cas::content::Address
+};
use hdk_proc_macros::zome;
これでコードがこのようになります。
Personエントリを作ろう
次に、UI側で実際にPersonエントリを作成する方法が必要です。Holochainにはhc_publicと呼ばれる概念があります。これは、hc_public
で定義されたZome関数をZome外部から呼び出せるようにランタイムに伝える役目を持っています。
上記のperson_entry_def
関数の下に次の行を追加します。
Person
を引数として受け取り、Address
の結果を返すパブリック関数を追加しましょう。
#[zome_fn(“hc_public”)]
pub fn create_person(person: Person) -> ZomeApiResult<Address> {
person引数からエントリを次の行で作成します。
let entry = Entry::App(“person”.into(), person.into());
次に、エントリをローカルソースチェーンにコミットします。
let address = hdk::commit_entry(&entry)?;
最後に、コミットされたPersonエントリのアドレスとともにResult型のOk
値を返します。
Ok(address)
}
これでコードがこのようになります。
コンパイル
コンパイル時でエラーがまだあるか確認しましょう。
nix-shell
で実行Personエントリを取得しよう
最後に、UI側から、ソースチェーンよりPersonエントリを取得する方法が必要になります。
create_person
関数の真下に次の行を追加してください。
Address
を引数としてPerson
エントリを返すパブリックであるretrieve_person
関数です。
ローカルストレージからそのエントリのアドレスを使用してエントリを取得し、Personの型であるstruct(構造体)に変換します。
hdk::utils::get_as_type(address)
}
これでコードがこのようになります。
直接コンパイルする代わりに、最初に作成したテストを実行してみましょう(テストは常に実行前にコンパイルします)。
nix-shell
で実行# tests 5
# pass 5
# ok
UI
バックエンドが上手く機能するようになったので、UIを変更して、作成したzome関数とやり取りしてみましょう。まず最初にコードをきれいにしましょう。まずは、前回のチュートリアルのJavaScriptを独自のファイルに移動しましょう。
Hello GUIチュートリアルで作成したGUIプロジェクトフォルダに移動します。
cd holochain/coreconcepts/gui
新しいhello.js
ファイルを作成し、お気に入りのエディターで開き、index.html
も開きます。
<script>
タグ内のすべてのコードをhello.js
に移動します。
— index.html
<script type=”text/javascript” >
– var holochain_connection = holochainclient.connect({ url: “ws://localhost:3401”});
–
– function hello() {
– holochain_connection.then(({callZome, close}) => {
– callZome(‘test-instance’, ‘hello’, ‘hello_holo’)({“args”: {} }).then((result) => update_span(result))
– })
– }
– function show_output(result) {
– var span = document.getElementById(‘output’);
– var output = JSON.parse(result);
– span.textContent = ” ” + output.Ok;
– }
</script>
+++ hello.js
+var holochain_connection = holochainclient.connect({ url: “ws://localhost:3401”});
+
+function hello() {
+ holochain_connection.then(({callZome, close}) => {
+ callZome(‘test-instance’, ‘hello’, ‘hello_holo’)({“args”: {} }).then((result) => update_span(result))
+ })
+}
+
+function show_output(result) {
+ var span = document.getElementById(‘output’);
+ var output = JSON.parse(result);
+ span.textContent = ” ” + output.Ok;
src
属性値を<script>
タグに追加します。
<script type=“text/javascript” src=“hello.js”></script>
Person用のUIウィジェットを作ろう
index.htmlで、HTML要素を追加してPersonを作成することから始めます。
前回の「say hello」要素を探してください。
<button onclick=“hello()” type=“button”>Say Hello</button>
<div>Response:</span><span id=“output”></div>
<!– ここに下記のコードを追加してください –>
<div>タグの真下に、セクションに見出しを付けるコードを追加しましょう。
<h3>Create a person</h3>
ユーザーが名前を入力できるようにテキストボックスを次に追加します。
<input type=“text” id=“name” placeholder=“Enter your name :)”>
create_person
という(まだ書かれていない)JavaScript関数を呼び出すボタンを次に追加します。
<button onclick=“create_person()” type=“button”>Submit Name</button>
idがaddress_output
であるspanを追加して、create_person()を呼び出した時の結果をレンダリングできるようにします。
<div>Address: <span id=“address_output”></span></div>
これでコードが以下のようになります。インデントされていないので気を付けてください。
hello.jsファイルに移ろう
Zomeを呼び出すcreate_person
関数を作りましょう。
まずはcreate_person
関数を追加します。
function create_person() {
次に、ID名name
でテキストボックスを取得し、現在のテキスト値をname変数に保存しましょう。
const name = document.getElementById(‘name’).value;
接続完了を待ってから、Zome呼び出しを行います。
holochain_connection.then(({callZome, close}) => {
hello
zomeでcreate_person
を呼び出し、Person
structの一部である、name
変数を渡し、結果をコンソールに出力します。
callZome(‘test-instance’, ‘hello’, ‘create_person’)({
person: {name: name},
}).then(result => console.log(result));
});
}
これでhello.js
が以下のようになります。
サーバを実行してブラウザを開こう
さあ、関数の呼び出しをテストしましょう。
新しいターミナルウィンドウを開き、nix-shellに入ってください。
cd holochain/coreconcepts/gui
nix-shell https://holochain.love
サーバを実行します。
nix-shell
で実行python -m SimpleHTTPServer
他のターミナル(バックエンドコードがある方)でパッケージを作成し、zomeを実行します。
nix-shell
で実行hc package
hc run -p 3401
UIサーバとHolochainコンダクターサーバの両方が実行されているので、ブラウザを開いて0.0.0.0:8000
に移動します。作成したHTML要素が表示されるはずです。
次に、ブラウザの開発者コンソールを開き、自分の名前を入力して、[Submit Name]ボタンを押します。次のようなものが見えるはずです。
注:もちろん、読者は自分の名前を入力したため、表示されるアドレスはおそらく異なるでしょう。
新しいエントリのアドレスを表示しよう
次に、開発者コンソールではなく、ページにアドレスを表示します。
しかし、最初に、少しコードをリファクタリングしましょう。 show_ouput
関数をより汎用的にすると、zome関数の出力を表示する各要素で再利用できます。
関数を再利用できるように、要素のIDを引数として渡します。
-function show_output(result) {
+function show_output(result, id) {
– var span = document.getElementById(‘output’);
+ var el = document.getElementById(id);
var output = JSON.parse(result);
– span.textContent = ‘ ‘ + output.Ok;
+ el.textContent = ‘ ‘ + output.Ok;
}
function hello() {
holochain_connection.then(({callZome, close}) => {
callZome(‘test-instance’, ‘hello’, ‘hello_holo’)({args: {}}).then(result =>
– show_output(result),
+ show_output(result, id),
);
});
}
function create_person() {
const name = document.getElementById(‘name’).value;
holochain_connection.then(({callZome, close}) => {
callZome(‘test-instance’, ‘hello’, ‘create_person’)({
person: {name: name},
– }).then(result => console.log(result));
+ }).then(result => show_output(result, id));
});
}
ブラウザに入ろう
ブラウザに戻り、ページを更新します。今回は、名前を入力して[Submit Name]を押すと、アドレスが表示されるようになったでしょう。
Personエントリを取得してUIに表示する
さて、index.html
ファイルに戻り、create personセクションの下(アドレスが表示されるその下)に、新しいヘッダーを追加します。
<h3>Retrieve Person</h3>
ユーザーがcreate_person
関数から返されるアドレスを入力できるようにテキストボックスを追加します。
<input type=“text” id=“address_in” placeholder=“Enter the entry address“>
(まだ記述されていない)retrieve_person
JavaScript関数を呼び出すボタンを次に追加します。
<button onclick=“retrieve_person()” type=“button”>Get Person</button>
IDがperson_output
であるspanを追加して、retrieve_person
関数から返されるPerson
エントリを表示します。
<div>Person: <span id=“person_output”></span></div>
hello.jsに移ろう
次に、hello.jsでretrieve_person関数を追加して、同じretrieve_person
という名前のzome関数を呼び出し、そのレスポンスを表示します。
function retrieve_person() {
次の行でaddress_in
テキストボックスから値を取得します。
var address = document.getElementById(‘address_in’).value;
次の行で接続完了を待ってから、Zomeの呼び出しを行います。
holochain_connection.then(({callZome, close}) => {
次の行でretrieve_person
というパブリックzome関数を呼び出して、そこにテキストインプットからのアドレスを渡します。次に、結果をshow_person
関数に渡します。
callZome(‘test-instance’, ‘hello’, ‘retrieve_person’)({
address: address,
}).then(result => show_person(result, ‘person_output’));
});
}
そして、show_person
関数を追加します。name
を表示する以外はshow_output
と非常に似ています。
function show_person(result) {
var person = document.getElementById(‘person_output’);
var output = JSON.parse(result);
person.textContent = ‘ ‘ + output.Ok.name;
}
ブラウザに入ろう
これでindex.htmlはこうなります。
そしてhello.jsはこのようになるはずです。
最後に、0.0.0.0:8000
でテストします。
次のように表示されるはずです。
おめでとうございます! GUIを使用して、プライベートソースチェーンからデータを保存および取得しました!