Mastodonを読む

はじめに

routes.rbを確認することで、「/」にアクセスしたときに実行されるのはHomeControllerのindexメソッドということがわかりました。それでは該当ファイルを確認してMastodonの処理を確認していきましょう。

app/controllers/home_controller.rb

というわけで、HomeControllerのindexメソッドを見てみましょう。

Everything is expanded.Everything is shortened.
  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です。また別の言語・・・

Everything is expanded.Everything is shortened.
  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

Everything is expanded.Everything is shortened.
  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

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
-
|
|
-
!
class Account < ApplicationRecord
  include Targetable
 
  # Local users
  has_one :user, inverse_of: :account

となっています。account.rbを見ると以降、フォローの関連付けなどが書かれており、こちらがMastodon的なユーザ情報(アカウント)であることがわかります。

では何故current_accountとして現在のアカウントが取れるかというと、

app/controllers/application_controller.rb

Everything is expanded.Everything is shortened.
  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タグも含めた出力は以下の通り。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
 
 
 
 
<body class='app-body'>
<div data-react-class="Mastodon" data-react-props="{&quot;locale&quot;:&quot;ja&quot;}" class="app-holder">div>
 
body>

ここから先、長期戦が見込まれるので一旦ここまで。

おわりに

というわけで、「/」にアクセスしたときに実行されるhome#indexを見てきました。現時点では出力されるHTMLとして空っぽの状態です。次回以降、React(とRedux)を使用したコードを読んでいくことで実際に目にしている画面がどのように作られているのか見ていきます。

感想として、ここまででHAML、RABLと二種類の言語(ひとつはRubyを使ったJSON出力のためのDSL)が出てきました。最先端の世界ではRuby/HTML/Javascriptを知ってるだけでは駄目で、より書きやすいものを求めて精進が必要なのだなぁと思いました。


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2017-05-03 (水) 19:07:29 (2549d)