# PakyowUI
Auto-Updating UIs for Pakyow, without moving to the client.
## Rationale
We wanted a way to build modern apps in a traditional backend-driven
manner while still reaping the benefits of modern, live updating
UIs. The existing approaches to building live UIs tend to *replace*
backend-driven architecture rather than *extend* it.
Instead of replacing traditional architecture, PakyowUI adds a layer
on top of it. This allows Pakyow apps to have live UIs out of the box
without any additional work by the developer, while remaining accessible
and fully usable in the absence of WebSockets, JavaScript, etc.
The PakyowUI approach stays true to the fundamental nature of the web.
It aims to be the real-time web expressed as progressive enhancement.
It allows for any aspect of a website or web app to be updated in
real-time, without any additional work by the developer.
## Overview
At a high level, PakyowUI keeps rendered views (meaning views that
are currently rendered by a client in the browser) in sync with the
current state of the data.
During the initial request/response cycle, Pakyow keeps track of what
view is rendered and sent back to the client, along with the underlying
data used to render those views.
When the data changes in the future, a set of transformation
instructions are sent to the client(s) who possess views with that
data. The transformations are then applied to the existing views by a
JavaScript client library so that the view not reflects the current
app state. The app *does not* push re-rendered views back down to the
client, nor does any JavaScript transcompilation occur.
---
Let's look at an example. Say during the initial request/response cycle
Pakyow rendered a view that presents a user's name:
```html
Bob Dylan
```
When the name changes, PakyowUI builds up an instruction like this:
```ruby
[[:bind, { name: 'Thelonius Monk' }]]
```
This instruction is routed to the client(s) that present a view
containing data for user 1. Once received, the intructions are applied.
Predictably, the updated view reflects the new state:
```html
Thelonius Monk
```
PakyowUI builds on the [pakyow-realtime
library](https://github.com/pakyow/pakyow/tree/master/pakyow-realtime),
so all of the communication between client and server occurs over a
WebSocket. If WebSockets aren't supported by the client (or for some
reason aren't working) the app will continue to work, just without live
updates. You get this graceful degradation for free without developing
with progressive enhancement in mind.
---
By expressing view transformations as data, they can be applied to
any view by any interpreter; be it on the server or the client. To
accomplish this, view rendering must be expressed separately from
the view and in context of the data being presented by the view. The
rendering itself also is expressed independently of how to fetch
the data necessary to perform the render, giving PakyowUI all the
information it needs to automatically perform the rendering again at
some point in the future.
## Data - Mutables
PakyowUI introduces a concept called **mutables**. A mutable wraps a
data model and defines two things:
1. Actions that can occur on the model that cause state mutations.
2. Queries that define how particular data is to be fetched.
Here's how a mutable is defined:
```ruby
class User < Sequel::Model; end
Pakyow::App.mutable :user do
model User
action :create do |object|
User.create(object)
end
query :all do
User.all
end
query :find do |id|
User[id]
end
end
```
From a route, we can use the mutable to query for data:
```ruby
# get all the users
data(:user).all
# get a specific user
data(:user).find(1)
```
We can also change data through the mutable:
```ruby
data(:user).create(params[:user])
```
Mutables are the first step in making the route declarative (what)
rather than imperative (how). All of the *how* is wrapped up in the
mutable itself, letting us express only *what* should happen from the
route. This is important.
## View - Mutators
The second concept introduced by PakyowUI is **mutators**. A mutator
describes *how* to render a particular view with some particular data.
Here's a mutator for rendering a list of users:
```ruby
Pakyow::Mutators :user do
mutator :list do |view, users|
view.apply(users)
end
end
```
From a route, you could invoke the mutator on a view like this:
```ruby
view.scope(:user).mutate(:list, with: data(:user).all)
```
Notice that we're mutating with the data from our mutable user. Pakyow
will fetch the data using the `all` query and pass it to the `list`
mutation where the view is rendered.
***NOTE:*** One important caveat here is that the individual data
passed to the mutate method needs to respond to the `to_hash` method,
which should return a hash of all relevant attributes. E.g. For
ActiveRecord models could define `to_hash` like this:
```
class User < ActiveRecord::Base
def to_hash
attributes
end
end
```
At this point we've effectively turned view rendering into a declarative
action from the route's point of view. We only have to describe *what*
we want to happen and Pakyow takes care of the rest.
This becomes useful when you want to subscribe the mutation to future
changes in state. You can do this by calling `subscribe`:
```ruby
view.scope(:user).mutate(:list, with: data(:user).all).subscribe
```
The view is rendered in the intial request/response cycle exactly like
it was before, but now it's also subscribed to any future state change
that affects the rendered view.
Let's mutate our state:
```ruby
data(:user).create(params[:user])
```
Pakyow knows that we've mutated our user state; it also knows what
clients are currently rendering the mutated user state. It automatically
pushes down instructions over a WebSocket describing how the client
should update their view to match the current state.
Our rendered views now keep themselves up to date with the current state
of the application; and we as the developer don't have to do anything
but write the initial rendering code! We don't have to move any part of
our app to the client. Our app retains a backend-driven architecture
while still behaving like a modern app with live updates.
## Ring - Client Library
PakyowUI ships with a client library called Ring, effectively
bringing Pakyow's view transformation API to the client. In addition
to applying view transformations, Pakyow.js also ships with several
components, including:
- Mutation Detection: Watches user-interaction with the rendered view and can
interpret which interactions cause a mutation in state (e.g. submitting a
form). Once detected, the mutation is sent to the server by calling the REST
endpoint through the open WebSocket. The mutation is then validated by the
server, persisted (if necessary), and broadcast to all other clients.
You can use Ring to build custom front-end components that emit
their own mutations or otherwise communicate with your app's HTTP routes
over a WebSocket.
Ring is available here:
- http://github.com/pakyow/ring).
## Channels
Pakyow keeps track of what clients should receive what state mutations
with channels. Here's how a channel is structured:
```
scope:{name};mutation{name}::{qualifiers}
```
In the example from the Mutators section, the subscribed channel name is:
```
scope:user;mutation:list
```
This means that any client who rendered any user data with the `list`
mutation will receive future updates in user state. Read the next
section to understand how to better control this.
## Qualifiers
You might be curious about how to exercise fine-grained control over
clients and the mutations they receive. PakyowUI handles this with
*qualifiers*.
For example, you can subscribe a view to only update with the current
user's data:
```ruby
view.scope(:user).mutate(:list, with:
data(:user).for_user(current_user)).subscribe({
user_id: current_user.id
})
```
The `user_id` qualifier is added to the channel name, so when future
mutations occur, the result will only be pushed down to that particular
client. Here's the subscribed channel name:
scope:user;mutation:present::user_id:1
You can also qualify mutators. Here's how you would express that you
want a particular user's mutations to be sent only to clients that
render that state:
```ruby
Pakyow::Mutators :user do
mutator :present, qualify: [:id] do |view, user|
view.bind(user)
end
end
view.scope(:user).mutate(:present, with: data(:user).find(1)).subscribe
```
The value for the qualifier will be pulled from the user's id and added to the
channel name. Now only client's who currently render the user with id of 1 will
receive future state changes about that user. Here's the subscribed channel
name:
```
scope:user;mutation:present::id:1
```
# Download
The latest version of Pakyow UI can be installed with RubyGems:
```
gem install pakyow-ui
```
Source code can be downloaded as part of the Pakyow project on Github:
- https://github.com/pakyow/pakyow/tree/master/pakyow-ui
# License
Pakyow UI is free and open-source under the [LGPLv3 license](https://choosealicense.com/licenses/lgpl-3.0/).
# Support
Documentation is available here:
- http://pakyow.org/docs/live-views
Found a bug? Tell us about it here:
- https://github.com/pakyow/pakyow/issues
We'd love to have you in the community:
- http://pakyow.org/get-involved