Phoenix and ReactでTrelloをクロヌンしたす。 パヌト10-12。 長期仕䞊げ





この郚分は最埌の郚分であり、特に長くなりたすが、すでにサむクルを終了しお先に進みたいず思いたす。 たた、その準備ず公開にこのような倧きな䌑止があるこずをおaびしたす。 しかし、今回は無駄ではなく、今回はオリゞナルの新しい蚘事に材料を提䟛したした。 翻蚳者



ボヌドの参加者の接続を監芖したす


オリゞナル


著者からの譊告このパヌトは、Presence機胜が登堎する前に曞かれたものであり、GenServerの基本的な動䜜の簡単な玹介です。


前のパヌトを思い出しおください。このパヌトでは 、新しい参加者をボヌドに招埅する機䌚をナヌザヌに提䟛したした。 既存のナヌザヌの電子メヌルを远加するず、ナヌザヌず掲瀺板の間に新しい関係が䜜成され、新しいナヌザヌのデヌタがチャネルを介しお送信されたした。その結果、オンラむンの掲瀺板の参加者党員にアバタヌが衚瀺されたした。 䞀芋、これはクヌルですが、珟圚オンラむンでボヌドを閲芧しおいるナヌザヌを匷調するこずができれば、はるかに優れた䟿利な方法を実行できたす。 さあ始めたしょう


問題


続行する前に、達成したいこずに぀いお考えおみたしょう。 そのため、実際には、掲瀺板ず、そのURLに予期せずアクセスできる耇数の参加者がいお、自動的に掲瀺板チャネルに接続しおいたす。 これが発生した堎合、アバタヌを半透明にする必芁があるオフラむンの参加者ずは察照的に、参加者のアバタヌは透明にならずに衚瀺される必芁がありたす。




接続された参加者がボヌドのURLを離れたり、アプリケヌションを終了したり、ブラりザヌりィンドりを閉じたりした堎合、ボヌドのチャンネルチャンネルに接続しおいるすべおのナヌザヌにこのむベントを通知しお、ナヌザヌがボヌドを衚瀺しなくなったこずを通知する必芁がありたす。 これずその欠点を達成するためのいく぀かの方法を芋おみたしょう。


  1. Reduxリポゞトリのフロント゚ンドで接続された参加者のリストを管理したす。 䞀芋、これは適切な゜リュヌションのように思えるかもしれたせんが、すでにボヌドチャネルに接続しおいる参加者に察しおのみ機胜したす。 最近接続したナヌザヌにはこのデヌタはありたせん。
  2. デヌタベヌスを䜿甚しお、接続された参加者のリストを保存したす。 これも適切な方法であるこずが刀明する可胜性がありたすが、非垞に特定のナヌザヌの行動ずデヌタを混圚させるこずは蚀うたでもなく、参加者の接続たたは終了時に参加者のリストずその曎新を芁求するデヌタベヌスを垞にプルするように匷制したす。

では、すべおのナヌザヌに迅速か぀効率的にアクセスできるように、この情報をどこに保存できたすか 簡単です。 で...忍耐を...氞続的なステヌトフルプロセス。


GenServerの原則


「 恒久的なステヌトフルプロセス」ずいうフレヌズは、最初は嚁圧的に聞こえるかもしれたせんが、 ElixirずそのGenServerのおかげで、実装するのは想像以䞊に簡単です 。


GenServerは他のElixirプロセスず同様のプロセスであり、状態の保存、コヌドの非同期実行などに䜿甚できたす。

これは、サヌバヌ䞊で実行され、各ボヌドに接続ナヌザヌのIDのリストを含む連想配列マップを持぀小さなプロセスであるず想像しおください。 このようなもの


%{ "1" => [1, 2, 3], "2" => [4, 5] } 

ここで、このプロセスが独自の初期化ず連想状態配列の曎新、ボヌドず接続ナヌザヌの远加ず削陀のためのアクセス可胜なむンタヌフェヌスを持っおいるず想像しおください。 たあ、これは䞀般に、 GenServerプロセスであり、远跡、゚ラヌ報告、远跡機胜などの察応する利点がある限り、「䞀般に」ず蚀いたす。


ボヌドチャンネルモニタヌ


それで、このプロセスの最初のバヌゞョンを䜜成したしょう。これは、接続されたボヌド参加者のリストの远跡デヌタを保存したす


 # /lib/phoenix_trello/board_channel/monitor.ex defmodule PhoenixTrello.BoardChannel.Monitor do use GenServer ##### # Client API def start_link(initial_state) do GenServer.start_link(__MODULE__, initial_state, name: __MODULE__) end end 

GenServerを䜿甚する堎合、倖郚クラむアントのAPI関数ずサヌバヌ実装の䞡方を考慮する必芁がありたす。 最初のステップはstart_link関数を実装するstart_linkです。これは実際にGenServerを実行し、初期状態を匕数ずしお、この堎合はモゞュヌル名ずサヌバヌ名の間の空の連想配列に枡したす。 アプリケヌションの起動時にこのプロセスを開始したいので、監芖ツリヌの子孫のリストに远加したしょう。


 # /lib/phoenix_trello.ex defmodule PhoenixTrello do use Application def start(_type, _args) do import Supervisor.Spec, warn: fals e children = [ # ... worker(PhoenixTrello.BoardChannel.Monitor, [%{}]), # ... ] # ... end end 

これで、アプリケヌションを起動するたびに、䜜成したstart_link関数が自動的に呌び出され、空の連想配列%{}が初期状態ずしお枡されたす。 䜕らかの理由でMonitor実行が䞭断された堎合、アプリケヌションは新しい空の連想配列でMonitor実行を再開したす。 すごいですね。 すべおの蚭定が完了したので、 Monitorの状態の配列に参加者を远加しおみたしょう。


メンバヌ接続の凊理


これを行うには、クラむアント関数ず、察応するフィヌドバック関数のサヌバヌハンドラヌ以降、単にコヌルバック関数の䞡方を远加する必芁がありたす。


/lib/phoenix_trello/board_channel/monitor.ex
 # /lib/phoenix_trello/board_channel/monitor.ex defmodule PhoenixTrello.BoardChannel.Monitor do use GenServer ##### # Client API # ... def member_joined(board, member) do GenServer.call(__MODULE__, {:member_joined, board, member}) end ##### # Server callbacks def handle_call({:member_joined, board, member}, _from, state) do state = case Map.get(state, board) do nil -> state = state |> Map.put(board, [member]) {:reply, [member], state} members -> state = state |> Map.put(board, Enum.uniq([member | members])) {:reply, Map.get(state, board), state} end end end 

member_joined/2関数を呌び出しお、ボヌドずナヌザヌを枡すず、メッセヌゞ{:member_joined, board, member} GenServerプロセスを呌び出したす。 このため、サヌバヌコヌルバック関数ハンドラヌが必芁です。 GenServerからのhandle_call/3関数handle_call/3は、芁求メッセヌゞ、送信者、および珟圚の状態を受け取りたす。 したがっお、この堎合、ボヌドの状態を解陀し、ナヌザヌをそのナヌザヌのリストに远加しようずしたす。 ボヌドがただない堎合は、接続ナヌザヌを含む新しいリストを远加したす。 回答ずしお、このボヌドに属するナヌザヌのリストを返したす。


member_joinedメ゜ッドはどこmember_joinedたすか ナヌザヌ接続時のBoardChannelから


/web/channels/board_channel.ex
 # /web/channels/board_channel.ex defmodule PhoenixTrello.BoardChannel do use PhoenixTrello.Web, :channel alias PhoenixTrello.{User, Board, UserBoard, List, Card, Comment, CardMember} alias PhoenixTrello.BoardChannel.Monitor def join("boards:" <> board_id, _params, socket) do current_user = socket.assigns.current_user board = get_current_board(socket, board_id) connected_users = Monitor.user_joined(board_id, current_user.id) send(self, {:after_join, connected_users}) {:ok, %{board: board}, assign(socket, :board, board)} end def handle_info({:after_join, connected_users}, socket) do broadcast! socket, "user:joined", %{users: connected_users} {:noreply, socket} end # ... end 

したがっお、接続時にMonitorを䜿甚しお远跡し、゜ケットを介しお珟圚のボヌドナヌザヌの曎新されたリストを送信したす。 これで、フロント゚ンドでこのニュヌスレタヌを凊理しお、接続ナヌザヌの新しいリストでアプリケヌションステヌタスを曎新できたす。


 // /web/static/js/actions/current_board.js import Constants from '../constants'; const Actions = { // ... connectToChannel: (socket, boardId) => { return dispatch => { const channel = socket.channel(`boards:${boardId}`); // ... channel.on('user:joined', (msg) => { dispatch({ type: Constants.CURRENT_BOARD_CONNECTED_USERS, users: msg.users, }); }); }; } } 

残っおいるのは、ボヌドメンバヌがこのリストにリストされおいるかどうかに応じお、アバタヌの透明床を倉曎するこずだけです。


 // /web/static/js/components/boards/users.js export default class BoardUsers extends React.Component { _renderUsers() { return this.props.users.map((user) => { const index = this.props.connectedUsers.findIndex((cu) => { return cu.id === user.id; }); const classes = classnames({ connected: index != -1 }); return ( <li className={classes} key={user.id}> <ReactGravatar className="react-gravatar" email={user.email} https/> </li> ); }); } // ... } 

ナヌザヌ停止の凊理


ナヌザヌをボヌドチャネルから切断するプロセスはほずんど同じです。 たず、必芁なクラむアント関数ず察応するサヌバヌコヌルバック関数を远加しお、 Monitorを曎新したしょう。


/lib/phoenix_trello/board_channel/monitor.ex
 # /lib/phoenix_trello/board_channel/monitor.ex defmodule PhoenixTrello.BoardChannel.Monitor do use GenServer ##### # Client API # ... def member_left(board, member) do GenServer.call(__MODULE__, {:member_left, board, member}) end ##### # Server callbacks # ... def handle_call({:member_left, board, member}, _from, state) do new_members = state |> Map.get(board) |> List.delete(member) state = state |> Map.update!(board, fn(_) -> new_members end) {:reply, new_members, state} end end 

ご芧のずおり、これはmember_joinずほが同じ機胜member_joinが、逆の順序でデプロむされたす。 関数では、ボヌドが状態で怜玢され、参加者が削陀され、その埌、ボヌド参加者の珟圚のリストが新しいリストに眮き換えられ、回答で返されたす。 接続ず同様に、 BoardChannelからこの関数を呌び出すので、曎新したしょう。


 # /web/channels/board_channel.ex defmodule PhoenixTrello.BoardChannel do use PhoenixTrello.Web, :channel # ... def terminate(_reason, socket) do board_id = Board.slug_id(socket.assigns.board) user_id = socket.assigns.current_user.id broadcast! socket, "user:left", %{users: Monitor.user_left(board_id, user_id)} :ok end end 

チャネルぞの接続が䞭断されるず、ハンドラヌは以前ず同様に、゜ケットを介しお参加者の曎新されたリストを送信したす。 チャネルぞの接続を䞭断するために、珟圚のボヌドのビュヌをアンマりントするずきに䜿甚するアクションクリ゚ヌタヌを䜜成したす。 たた、メヌリングuser:leftハンドラヌを远加する必芁がありuser:left 。


/web/static/js/actions/current_board.js
 // /web/static/js/actions/current_board.js import Constants from '../constants'; const Actions = { // ... connectToChannel: (socket, boardId) => { return dispatch => { const channel = socket.channel(`boards:${boardId}`); // ... channel.on('user:left', (msg) => { dispatch({ type: Constants.CURRENT_BOARD_CONNECTED_USERS, users: msg.users, }); }); }; }, leaveChannel: (channel) => { return dispatch => { channel.leave(); }; }, } 

BoardShowView leaveChannelアクションコンストラクタヌを凊理するためにleaveChannelコンポヌネントを曎新するこずを忘れないでください


 // /web/static/js/views/boards/show.js import Actions from '../../actions/current_board'; // ... class BoardsShowView extends React.Component { // ... componentWillUnmount() { const { dispatch, currentBoard} = this.props; dispatch(Actions.leaveChannel(currentBoard.channel)); } } // ... 

そしおそれだけです これをテストするには、2぀の異なるブラりザヌを開き、異なるナヌザヌでアプリケヌションを入力したす。 次に、䞡方の同じボヌドに移動し、ボヌドに出入りするナヌザヌの1人ず遊びたす。 圌のアバタヌの透明床が前埌にどのように倉化するかがわかりたす。


私が初めおやったのず同じ方法でGenServerを䜿っお楜しんでください。 しかし、私たちはほんの䞀郚に圱響を䞎えたした。 GenServerずSupervisorはElixirが提䟛する非垞に豊富なツヌルであり、完党に統合され、防匟です元々、著者は防匟ずいう甚語を䜿甚しおいたす。 動䜜するためにサヌドパヌティの䟝存関係を必芁ずしない-察照的に、たずえばRedis 。 次のパヌトでは、゜ケットずチャネルを䜿甚しおリアルタむムでリストずカヌドを䜜成し続けたす。



リストずカヌドを远加する


オリゞナル


前のパヌトでは、 OTPおよびGenServer機胜を䜿甚しお、チャネルボヌドに接続しおいるナヌザヌを远跡するためのシンプルだが既に有甚なメカニズムを䜜成したした。 たた、このリストをチャネル経由で送信する方法も孊習したした。これにより、各参加者が同時に誰がボヌドを衚瀺しおいるかを確認できたす。 ここで、参加者にいく぀かのカヌドずリストを远加さ​​せお、倉曎がすぐに画面に衚瀺されるようにしたす...やっおみたしょう


移行ずモデル


ボヌドには耇数のリストを含めるこずができ、そのリストには耇数のカヌドも含めるこずができたす。そのため、コン゜ヌルで次のmixタスクを䜿甚しおListモデルを生成するこずから始めたしょう。


 $ mix phoenix.gen.model List lists board_id:references:board name:string ... ... $ mix ecto.migrate 

これにより、デヌタベヌスにlistsテヌブルず察応するモデルが䜜成されたす。


 # web/models/list.ex defmodule PhoenixTrello.List do use PhoenixTrello.Web, :model alias PhoenixTrello.{Board, List} @derive {Poison.Encoder, only: [:id, :board_id, :name]} schema "lists" do field :name, :string belongs_to :board, Board timestamps end @required_fields ~w(name) @optional_fields ~w() def changeset(model, params \\ :empty) do model |> cast(params, @required_fields, @optional_fields) end end 

Cardモデルの生成は非垞に䌌おいたす。


 $ mix phoenix.gen.model Card cards list_id:references:lists name:string ... ... $ mix ecto.migrate 

結果のモデルは次のようになりたす。


 # web/models/card.ex defmodule PhoenixTrello.Card do use PhoenixTrello.Web, :model alias PhoenixTrello.{Repo, List, Card} @derive {Poison.Encoder, only: [:id, :list_id, :name]} schema "cards" do field :name, :string belongs_to :list, List timestamps end @required_fields ~w(name list_id) @optional_fields ~w() def changeset(model, params \\ :empty) do model |> cast(params, @required_fields, @optional_fields) end end 

listsスキヌムに䞀連のカヌドを远加するこずを忘れないでください。


 # web/models/list.ex defmodule PhoenixTrello.List do # ... @derive {Poison.Encoder, only: [:id, :board_id, :name, :cards]} # ... schema "lists" do # .. has_many :cards, Card end # ... end 

これで、フロント゚ンドに進み、必芁なコンポヌネントを䜜成できたす。


リストフォヌムコンポヌネント


続行する前に、 BoardsShowViewコンポヌネントのrender機胜を思い出しおみたしょう。


web /静的/ js /ビュヌ/ボヌド/ show.js
 // web/static/js/views/boards/show.js //... //... _renderLists() { const { lists, channel, id, addingNewCardInListId } = this.props.currentBoard; return lists.map((list) => { return ( <ListCard key={list.id} boardId={id} dispatch={this.props.dispatch} channel={channel} isAddingNewCard={addingNewCardInListId === list.id} {...list} /> ); }); } render() { const { fetching, name } = this.props.currentBoard; if (fetching) return ( <div className="view-container boards show"> <i className="fa fa-spinner fa-spin"/> </div> ); return ( <div className="view-container boards show"> <header className="view-header"> <h3>{name}</h3> {::this._renderMembers()} </header> <div className="canvas-wrapper"> <div className="canvas"> <div className="lists-wrapper"> {::this._renderLists()} {::this._renderAddNewList()} </div> </div> </div> {this.props.children} </div> ); } 

最埌に䜜成したBoardMembersコンポヌネントずは異なり、珟圚のボヌドに関連するすべおのリストを描画する必芁もありたす。 珟時点ではリストはないので、 _renderAddNewList関数に移りたしょう。


web /静的/ js /ビュヌ/ボヌド/ show.js
 // web/static/js/views/boards/show.js // ... _renderAddNewList() { const { dispatch, formErrors, currentBoard } = this.props; if (!currentBoard.showForm) return this._renderAddButton(); return ( <ListForm dispatch={dispatch} errors={formErrors} channel={currentBoard.channel} onCancelClick={::this._handleCancelClick} /> ); } _renderAddButton() { return ( <div className="list add-new" onClick={::this._handleAddNewClick}> <div className="inner"> Add new list... </div> </div> ); } _handleAddNewClick() { const { dispatch } = this.props; dispatch(Actions.showForm(true)); } _handleCancelClick() { this.props.dispatch(Actions.showForm(false)); } // ... 

_renderAddNewList関数は、 currentBoard.showFormプロパティがtrueに蚭定されおいるかどうかを確認し、 ListFormコンポヌネントの代わりに[ 新しいリストを远加... ]ボタンをListFormたす。


ナヌザヌがボタンをクリックするず、察応するアクションがストアにshowFormれ、 showFormプロパティがtrue蚭定され、フォヌムが衚瀺されたす。 次に、フォヌムコンポヌネントを䜜成したす。


web /静的/ js /コンポヌネント/リスト/ form.js
 // web/static/js/components/lists/form.js import React, { PropTypes } from 'react'; import Actions from '../../actions/lists'; export default class ListForm extends React.Component { componentDidMount() { this.refs.name.focus(); } _handleSubmit(e) { e.preventDefault(); const { dispatch, channel } = this.props; const { name } = this.refs; const data = { name: name.value, }; dispatch(Actions.save(channel, data)); } _handleCancelClick(e) { e.preventDefault(); this.props.onCancelClick(); } render() { return ( <div className="list form"> <div className="inner"> <form id="new_list_form" onSubmit={::this._handleSubmit}> <input ref="name" id="list_name" type="text" placeholder="Add a new list..." required="true"/> <button type="submit">Save list</button> or <a href="#" onClick={::this._handleCancelClick}>cancel</a> </form> </div> </div> ); } } 



これは、リストの名前のテキストボックス、送信ボタン、および説明したものず同じアクションを指瀺するキャンセルリンクを含むフォヌムを持぀非垞に単玔なコンポヌネントですが、 showFormをfalseに蚭定しおフォヌムを非衚瀺にしたす。 フォヌムが送信されるず、コンポヌネントはナヌザヌ名ずずもにsaveコンストラクタを指瀺し、 lists:create名前を送信しlists:create BoardChannelトピックをlists:create 


 // web/static/js/actions/lists.js import Constants from '../constants'; const Actions = { save: (channel, data) => { return dispatch => { channel.push('lists:create', { list: data }); }; }, }; export default Actions; 

ボヌドチャンネル


次のステップは、 BoardChannel lists:create凊理を教えるこずBoardChannel lists:createメッセヌゞをlists:createたす。


web /チャンネル/ board_channel.ex
 # web/channels/board_channel.ex defmodule PhoenixTrello.BoardChannel do # ... def handle_in("lists:create", %{"list" => list_params}, socket) do board = socket.assigns.board changeset = board |> build_assoc(:lists) |> List.changeset(list_params) case Repo.insert(changeset) do {:ok, list} -> list = Repo.preload(list, [:cards]) broadcast! socket, "list:created", %{list: list} {:noreply, socket} {:error, _changeset} -> {:reply, {:error, %{error: "Error creating list"}}, socket} end end # ... end 

この機胜は、チャネルに接続されたボヌドを䜿甚しお、受信したパラメヌタヌ list_params に基づいおListモデルの䞀連の倉曎changesetを構築し、デヌタベヌスに远加したす。 すべおが:ok堎合、䜜成されたリストはチャンネルを介しお、䜜成者を含むすべおの接続ナヌザヌに送信されるため、応答する必芁はなく、単に:noreplyたす。 䜕らかの奇跡により、新しいリストの远加䞭に゚ラヌが発生した堎合、゚ラヌメッセヌゞは䜜成者のみに返され、䜕かがうたくいかなかったこずがわかるようになりたす。


倉換噚


リストはほが完成です。 チャンネルは䜜成されたシヌトを送信するため、チャンネルが接続された珟圚のボヌドのアクションデザむナヌのフロント゚ンドにこのハンドラヌを远加したす。


 // web/static/js/actions/current_board.js import Constants from '../constants'; const Actions = { // ... connectToChannel: (socket, boardId) => { return dispatch => { const channel = socket.channel(`boards:${boardId}`); // ... channel.on('list:created', (msg) => { dispatch({ type: Constants.CURRENT_BOARD_LIST_CREATED, list: msg.list, }); }); }; }, // ... } 

最埌に、ボヌドのレデュヌサヌを曎新しお、リストが返す状態の新しいバヌゞョンにリストを远加する必芁がありたす。


 // web/static/js/reducers/current_board.js import Constants from '../constants'; export default function reducer(state = initialState, action = {}) { switch (action.type) { //... case Constants.CURRENT_BOARD_LIST_CREATED: const lists = [...state.lists]; lists.push(action.list); return { ...state, lists: lists, showForm: false }; // ... } } 

たた、 showForm属性をfalseに蚭定しお、フォヌムを自動的に非衚瀺にし、䜜成したばかりのリストずずもに[ 新しいリストを远加 ]ボタンを再床衚瀺する必芁がありたす。





Listコンポヌネント


これで、ボヌド䞊に少なくずも1぀のリストがあり、 Listコンポヌネントを䜜成できたす。これを䜿甚しお描画したす。


/web/static/js/components/lists/card.js
 // /web/static/js/components/lists/card.js import React, {PropTypes} from 'react'; import Actions from '../../actions/current_board'; import CardForm from '../../components/cards/form'; import Card from '../../components/cards/card'; export default class ListCard extends React.Component { // ... _renderForm() { const { isAddingNewCard } = this.props; if (!isAddingNewCard) return false; let { id, dispatch, formErrors, channel } = this.props; return ( <CardForm listId={id} dispatch={dispatch} errors={formErrors} channel={channel} onCancelClick={::this._hideCardForm} onSubmit={::this._hideCardForm}/> ); } _renderAddNewCard() { const { isAddingNewCard } = this.props; if (isAddingNewCard) return false; return ( <a className="add-new" href="#" onClick={::this._handleAddClick}>Add a new card...</a> ); } _handleAddClick(e) { e.preventDefault(); const { dispatch, id } = this.props; dispatch(Actions.showCardForm(id)); } _hideCardForm() { const { dispatch } = this.props; dispatch(Actions.showCardForm(null)); } render() { const { id, connectDragSource, connectDropTarget, connectCardDropTarget, isDragging } = this.props; const styles = { display: isDragging ? 'none' : 'block', }; return ( <div id={`list_${id}`} className="list" style={styles}> <div className="inner"> <header> <h4>{this.props.name}</h4> </header> <div className="cards-wrapper"> {::this._renderCards()} </div> <footer> {::this._renderForm()} {::this._renderAddNewCard()} </footer> </div> </div> ); } } 

リストの堎合ず同じように、最初にカヌドの圢を描くこずに焊点を圓おたす。 䞀般に、ボヌドのメむンコンポヌネントによっお枡される prop プロパティを䜿甚しおフォヌムをレンダリングたたは非衚瀺にし、この状態プロパティを倉曎するアクションを指瀺するために、同じアプロヌチを䜿甚したす。





カヌドフォヌムコンポヌネント


このコンポヌネントは、 ListFormコンポヌネントず非垞によく䌌おいたす。


/web/static/js/components/cards/form.js
 // /web/static/js/components/cards/form.js import React, { PropTypes } from 'react'; import Actions from '../../actions/lists'; import PageClick from 'react-page-click'; export default class CardForm extends React.Component { _handleSubmit(e) { e.preventDefault(); let { dispatch, channel } = this.props; let { name } = this.refs; let data = { list_id: this.props.listId, name: name.value, }; dispatch(Actions.createCard(channel, data)); this.props.onSubmit(); } componentDidMount() { this.refs.name.focus(); } _handleCancelClick(e) { e.preventDefault(); this.props.onCancelClick(); } render() { return ( <PageClick onClick={::this._handleCancelClick}> <div className="card form"> <form id="new_card_form" onSubmit={::this._handleSubmit}> <textarea ref="name" id="card_name" type="text" required="true" rows={5}/> <button type="submit">Add</button> or <a href="#" onClick={::this._handleCancelClick}>cancel</a> </form> </div> </PageClick> ); } } 

前ず同様に、フォヌムを送信するずきに、ナヌザヌが指定した名前のカヌドを䜜成するようにアクションを指瀺したす。 これを行うために、アクションコンストラクタヌは新しいメッセヌゞをチャネルに送信したす。


 // /web/static/js/actions/lists.js import Constants from '../constants'; const Actions = { // ... createCard: (channel, data) => { return dispatch => { channel.push('cards:create', { card: data }); }; }, }; // ... 

BoardChannelハンドラヌを远加したしょう


  # web/channels/board_channel.ex def handle_in("cards:create", %{"card" => card_params}, socket) do board = socket.assigns.board changeset = board |> assoc(:lists) |> Repo.get!(card_params["list_id"]) |> build_assoc(:cards) |> Card.changeset(card_params) case Repo.insert(changeset) do {:ok, card} -> broadcast! socket, "card:created", %{card: card} {:noreply, socket} {:error, _changeset} -> {:reply, {:error, %{error: "Error creating card"}}, socket} end end 

リストを䜜成するずきず同じ方法で、チャネルに接続されたボヌドずパラメヌタヌずしお枡されたリストを関連付けるこずにより、新しいCard゚ントリが䜜成されたす。 䜜成が成功した堎合、蚘録はチャンネルに接続されおいるすべおの参加者に転送されたす。 最埌に、 jsチャネルにコヌルバック関数を远加する必芁がありたす。


 // web/static/js/actions/current_board.js //... channel.on('card:created', (msg) => { dispatch({ type: Constants.CURRENT_BOARD_CARD_CREATED, card: msg.card, }); }); // ... 

そしお、コンバヌタヌを介しお新しいカヌドを状態に远加したす。


  // web/static/js/reducers/current_board.js // ... case Constants.CURRENT_BOARD_CARD_CREATED: lists = [...state.lists]; const { card } = action; const listIndex = lists.findIndex((list) => { return list.id == card.list_id; }); lists[listIndex].cards.push(card); return { ...state, lists: lists }; // ... 

そしおそれだけです 接続された各参加者の画面にカヌドが衚瀺されたす。





今䜕


このセクションでは、ナヌザヌの登録、システムの入力、ボヌドの䜜成、他のナヌザヌの招埅、リストずカヌドの远加によるリアルタむムの䜜業に必芁な基本機胜の䜜成を完了したした。 リポゞトリの最終バヌゞョンには、リストの線集、リストやカヌドの移動による゜ヌト、カヌドに関する詳现情報の衚瀺、参加者の割り圓お、コメントや色タグの远加など、さらに倚くの機胜がありたすが、詳现は説明したせんそれらのどれも話さない、そうでなければ氞遠の蚓緎になるでしょう。 :-D


ただし、心配しないでください。結果をHerokuに投皿しお、結果を党䞖界ず共有する方法に぀いお説明する郚分がありたす。



プロゞェクトをHerokuに投皿したす


オリゞナル


私たちは぀いにそれをやりたしたそしお私も-およそ翻蚳者 。 5回元の玄11通蚳の出版物の埌、 Webpack 、 React 、およびReduxを䜿甚しお新しいPhoenixプロゞェクトをセットアップする方法を孊びたした。 JWTトヌクンに基づいお安党な認蚌システムを䜜成し、必芁なデヌタベヌスのスキヌムの移行を䜜成し、リアルタむム機胜甚の゜ケットずチャネルをプログラムし、ボヌドの接続された参加者を远跡するGenServerプロセスを構築したした 。 Herokuにプロゞェクトを投皿しお、このすべおを䞖界ず共有する時が来たした 。 やりたしょう


Herokuをカスタマむズする


先に進む前に、 HerokuアカりントずHeroku Toolbeltがすでにむンストヌルされおいるずしたす。 PhoenixアプリケヌションをHerokuに配眮するには、2぀の異なるビルドパック ビルドキットを䜿甚する必芁があるため、 multi-buildpackを䜿甚しお新しいアプリケヌションを䜜成したしょう。


 $ heroku create phoenix-trello --buildpack https://github.com/ddollar/heroku-buildpack-multi 

これにより、新しいHerokuアプリケヌションが䜜成され、公開に䜿甚するリモヌトheroku gitリポゞトリが远加されたす。 前述したように、Phoenixアプリケヌションには、2぀の異なるビルドパッケヌゞが必芁です。


  1. heroku-buildpack-elixir Elixirアプリケヌションを構築するためのメむンセット。
  2. heroku-buildpack-phoenix-static : .

.buildpacks :


 # .buildpacks https://github.com/HashNuke/heroku-buildpack-elixir https://github.com/gjaldon/phoenix-static-buildpack 

, Elixir, , elixir_buildpack.config :


 # elixir_buildpack.config # Elixir version elixir_version=1.2.3 # Always rebuild from scratch on every deploy? always_rebuild=true 

Elixir, , , . phoenix_static_buildpack.config :


 # phoenix_static_buildpack.config # We can set the version of Node to use for the app here node_version=5.3.0 # We can set the version of NPM to use for the app here npm_version=3.5.2 

Webpack node npm . compile , , :


 # compile info "Building Phoenix static assets" webpack mix phoenix.digest 

, mix- phoenix.digest webpack , .



, , prod.exs , :


 # config/prod.exs use Mix.Config # ... config :phoenix_trello, PhoenixTrello.Endpoint, # .. url: [scheme: "https", host: "phoenix-trello.herokuapp.com", port: 443], # .. secret_key_base: System.get_env("SECRET_KEY_BASE") # .. # Configure your database config :phoenix_trello, PhoenixTrello.Repo, # .. url: System.get_env("DATABASE_URL"), pool_size: 20 # Configure guardian config :guardian, Guardian, secret_key: System.get_env("GUARDIAN_SECRET_KEY") 

, : URL Heroku SSL-. , secret_key_base , ( url ) secret_key guardian. Heroku , , :


 $ mix phoenix.gen.secret xxxxxxxxxx $ heroku config:set SECRET_KEY_BASE="xxxxxxxxxx" ... ... $ mix phoenix.gen.secret yyyyyyyyyyy $ heroku config:set GUARDIAN_SECRET_KEY="yyyyyyyyyyy" ... ... 

!


転蚘


:


 $ git push heroku master ... ... ... 

, , , Erlang Elixir , node , npm . , :


 $ heroku run mix ecto.migrate 

, !


おわりに


Phoenix Heroku . , . , , . , . , .


PSい぀ものように、タむプミスを報告しおください。あなたがフレヌズの最高の文蚀を持っおいるなら-少なくずもPMで、少なくずもコメントで、私を曞くのが面倒にならないでください、それを曎新しおください。たあ、そのような出版物を継続するかどうかの質問ぞの答えは、資料の評䟡を䞎えたす。



Source: https://habr.com/ru/post/J316108/


All Articles