Mastodonを読む
はじめに †
routes.rbを確認することで、「/」にアクセスしたときに実行されるのはHomeControllerのindexメソッドということがわかりました。それでは該当ファイルを確認してMastodonの処理を確認していきましょう。
app/controllers/home_controller.rb †
というわけで、HomeControllerのindexメソッドを見てみましょう。
1
2
3
4
5
6
7
8
9
10
| -
|
|
-
|
|
|
|
|
!
| class HomeController < ApplicationController
before_action :authenticate_user!
def index
@body_classes = 'app-body'
@token = find_or_create_access_token.token
@web_settings = Web::Setting.find_by(user: current_user)&.data || {}
@admin = Account.find_local(Setting.site_contact_username)
@streaming_api_base_url = Rails.configuration.x.streaming_api_base_url
end
|
うーん、シンプル。というかこれだけだと何もわかりません。ビューを見てみることにしましょう。
app/views/home/index.html.haml †
ビューはHAMLで書かれているようです。
1
2
3
4
5
6
| - content_for :header_tags do
%script#initial-state{:type => 'application/json'}!= json_escape(render(file: 'home/initial_state', formats: :json))
= javascript_include_tag 'application', integrity: true, crossorigin: 'anonymous'
= react_component 'Mastodon', default_props, class: 'app-holder', prerender: false
|
・・・。HAML使ったことないので、「Hamlの書き方」を参考にすると、
- 「%」はHTMLのタグ、「#」はid、「{}」は属性
- 「-」はブロック。インデントが戻るまでがブロックになる様子
- 「=」は変数やメソッド呼び出しなどの出力
「!=」ってなんだ。まあ雰囲気的にメソッド呼び出して結果を埋めこんでいるようですが。行頭じゃないとこう書くのかな。
なお、Ruby on Railsでは、views/layout/application.html.hamlに共通するレイアウトを書き、各コントローラのメソッドに対応するビューファイルにはパーツを書くということになっています。content_forを使うことで、・・・どう動いてるんだ?全体レイアウトの方にはyieldがシンボル指定ありとなしで書いてあるし、先に個別を動かして出力を取得しておいて後からyield呼ばれたときに対応するものをレンダリングしてるのか?Rails4読むの中断してしまったからわからん。まあそんな感じに動いてるんだろうということにしておきます(ぉぃ
app/views/home/initial_state.json.rabl †
home/initial_stateを見てみましょう。ちなみにこの部分はrenderメソッド呼び出し→対応するビューのファイルを読み込むという処理になっており、HTTPリクエストが送られているわけではありません。
対応するのはinitial_state.json.rablです。また別の言語・・・
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
-
|
|
|
|
|
|
|
|
|
|
!
-
|
|
|
|
!
-
|
|
|
|
!
| object false
node(:meta) do
{
streaming_api_base_url: @streaming_api_base_url,
access_token: @token,
locale: I18n.locale,
domain: site_hostname,
me: current_account.id,
admin: @admin.try(:id),
boost_modal: current_account.user.setting_boost_modal,
auto_play_gif: current_account.user.setting_auto_play_gif,
}
end
node(:compose) do
{
me: current_account.id,
default_privacy: current_account.user.setting_default_privacy,
}
end
node(:accounts) do
store = {}
store[current_account.id] = partial('api/v1/accounts/show', object: current_account)
store[@admin.id] = partial('api/v1/accounts/show', object: @admin) unless @admin.nil?
store
end
node(:settings) { @web_settings }
|
公式ページに書き方ガイドはありますが、それを参考にしても上のコードはあまり理解できないので、論より証拠と実際の出力を見てみましょう。(そのままは見にくいので改行を入れています)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
| {
"meta":{
"streaming_api_base_url":"http://localhost:4000",
"access_token":"8920c1d8279002fa0f6b74f96bf7c971f997318755a35f5a26b511229a2786b7",
"locale":"ja",
"domain":"mastodon.dev",
"me":1,
"admin":null,
"boost_modal":false,
"auto_play_gif":true
},
"compose":{
"me":1,
"default_privacy":"public"
},
"accounts":{
"1":{
"id":1,
"username":"admin",
"acct":"admin",
"display_name":"",
"locked":false,
"created_at":"2017-05-01T11:52:49.199Z",
"followers_count":0,
"following_count":0,
"statuses_count":1,
"note":"\u003cp\u003e\u003c/p\u003e",
"url":"http://mastodon.dev/@admin",
"avatar":"http://mastodon.dev/avatars/original/missing.png",
"avatar_static":"http://mastodon.dev/avatars/original/missing.png",
"header":"http://mastodon.dev/headers/original/missing.png",
"header_static":"http://mastodon.dev/headers/original/missing.png"
}
},
"settings":{
"onboarded":true,
"home":{
"shows":{
"reblog":true,"reply":true
}
},
"notifications":{
"alerts":{
"follow":true,
"favourite":true,
"reblog":true,
"mention":true
},
"shows":{
"follow":true,
"favourite":true,
"reblog":true,
"mention":true
},
"sounds":{
"follow":true,
"favourite":true,
"reblog":true,
"mention":true
}
}
}
}
|
というわけで、
- object falseによりルートの要素に名前を付けない
- nodeで指定されたシンボルが名前になり、ブロックの最後に書いたハッシュテーブルがJSONとして埋め込まれる(yieldしてるのでしょう)
という動作をしているようです。このinitial_stateはReduxを使った画面表示の際に使われるのでしょう。
アカウントの情報取得するために部分テンプレートが呼ばれていますがまあ大体わかると思うので省略します。
current_account †
initial_state.json.rabl中ではしれっとcurrent_accountという記述がありました。まあ、現在のログインアカウントなんだろうなということはわかりますが、deviseではUserが使われています。
対応関係を確認すると、
app/models/user.rb
1
2
3
4
5
6
7
8
9
10
11
| -
|
|
|
|
|
|
|
|
|
|
| class User < ApplicationRecord
include Settings::Extend
devise :registerable, :recoverable,
:rememberable, :trackable, :validatable, :confirmable,
:two_factor_authenticatable, :two_factor_backupable,
otp_secret_encryption_key: ENV['OTP_SECRET'],
otp_number_of_backup_codes: 10
belongs_to :account, inverse_of: :user, required: true
accepts_nested_attributes_for :account
|
app/models/account.rb
1
2
3
4
5
| -
|
|
-
!
| class Account < ApplicationRecord
include Targetable
has_one :user, inverse_of: :account
|
となっています。account.rbを見ると以降、フォローの関連付けなどが書かれており、こちらがMastodon的なユーザ情報(アカウント)であることがわかります。
では何故current_accountとして現在のアカウントが取れるかというと、
app/controllers/application_controller.rb
1
2
3
4
5
6
7
| -
|
|
|
-
|
!
| class ApplicationController < ActionController::Base
helper_method :current_account
def current_account
@current_account ||= current_user.try(:account)
end
|
とヘルパーメソッドが定義されているためです。
app/views/home/index.html.haml続き †
さて、index.html.hamlに戻って、再掲、
1
2
3
4
5
6
| - content_for :header_tags do
%script#initial-state{:type => 'application/json'}!= json_escape(render(file: 'home/initial_state', formats: :json))
= javascript_include_tag 'application', integrity: true, crossorigin: 'anonymous'
= react_component 'Mastodon', default_props, class: 'app-holder', prerender: false
|
bodyタグも含めた出力は以下の通り。
1
2
3
4
|
| <body class='app-body'>
<div data-react-class="Mastodon" data-react-props="{"locale":"ja"}" class="app-holder">div>
body>
|
ここから先、長期戦が見込まれるので一旦ここまで。
おわりに †
というわけで、「/」にアクセスしたときに実行されるhome#indexを見てきました。現時点では出力されるHTMLとして空っぽの状態です。次回以降、React(とRedux)を使用したコードを読んでいくことで実際に目にしている画面がどのように作られているのか見ていきます。
感想として、ここまででHAML、RABLと二種類の言語(ひとつはRubyを使ったJSON出力のためのDSL)が出てきました。最先端の世界ではRuby/HTML/Javascriptを知ってるだけでは駄目で、より書きやすいものを求めて精進が必要なのだなぁと思いました。