
Show HN: Vue-Model – Models with HTTP Actions for Vue.js - aarondf
https://github.com/aarondfrancis/vue-model
======
davidscolgan
I'm usually a Django backend developer doing the whole server-rendered static
html type sites, but I've attempted to venture into the world of single page
apps a few times. The thing that always tripped me up was figuring out the
frontend model layer. Nothing I've used, whether Redux or Vuex or Mobx or
whatever else seemed as elegant and simple as say the Django ORM is on the
backend. There were always missing pieces that I couldn't quite get working
cleanly, like validation, or loading indicators, etc.

This looks like it has the potential to be what I was hoping someone would
make. Looks excellent so far, thanks for sharing!

~~~
kostarelo
I strongly believe that you are used to thinking in the MVC way of thinking.
Redux (and alternatives) are not the classical MVC structure and it takes some
time to get used to it. In a backend environment, a controller might call a
Model's function, take some data and return them back. In a frontend app,
especially in the componentized world of React, things are quite different
because you don't have controllers, your components are deep nested in your
app's tree, they may query an API to get data or they may query the local
cache. A simple model per component/multiple components approach won't work.

Once you get used to it, you will actually see that it's even more flexible
and easy to write applications and can even benefit your backend code as well.
E.g., I have stopped creating models on my backend code and instead, I am
writing modules that may query a table, or two tables or talk to another API.
I am using them whenever it makes sense to, not just the controllers.

My background is from creating both backend and frontend apps and I hope the
above to holds true since I've been there but let me know if I am wrong here.

~~~
vog
To add to this:

The client model is different from the server model!

Of course, it is not totally different, but different enough to warrant
separate treatment, and the most differences are fundamental, not just
differences in detail.

The client model is mostly hierarchical, not a cross-connected graph, so it
contains multiple "tree-ifications" of the server model. Also, most parts of
the tree have much fewer fields, as not all data fields are meant to show up
in the client. And that set of fields might be different in different corners
of the client. One server model class might have multiple vastly different
representations in the client model, depending on the corner of the user
interface. On top of all that, the client model contains transitional
information about the state of the user interface that is not represented in
the server model at all, such as which item in a list is selected, which part
of a map is shown, which parts of a document are folded or opened, and so on.

Finally, the client model operates mostly in an "open world" fashion, while
the server model is a "closed world". I'm using these terms in the meaning of
Prolog terminology.

That is, the client must always assume that on server side, there are more
records that it currently has, and that each record may have more fields on
server side than the client is aware of. So with very few exceptions, the
client should always perform minimal changes (insert/update/delete), not bulk
changes. And after reach change, it should retrieve a fresh state from the
server, because another client might have changed something as well, and
because the server might have decided to perform some other changes (update
calculated/cached fields) as well.

The server, on the other side, usually has the whole database at its
fingertips, so it can safely perform large-scale changes, not just minimal
changes.

------
Zenbit_UX
Hey, this seems cool but I don't really know why...

New to vue.js and maybe I'm doing things... Wrong? But I don't think I need
this.

My approach is very angularJs-esque but I have a root component 'main.vue'
which basically acts as my index.html. When I want to send a REST call I've
got a method setup which handles GET calls and another for POST calls... I use
them how you would call an angular service, and it works... Well (at least I
thought so until seeing your post).

Can you explain why I'm stupid and everything I've done is wrong?

~~~
aarondf
> Can you explain why I'm stupid and everything I've done is wrong?

Haha I cannot, because you probably aren't stupid and haven't done everything
wrong.

The thing I like about this approach is that it removes the need for manually:
1) writing `saveCustomer` or `getCustomer` methods on my vue instance 2)
tracking validation errors for each model. So if there's a collection of
models on the page, I can super easily show errors for each 3) keeping track
of loading indicators 4) applying the server's response to the model.

In your setup, what happens if the HTTP call fails? You have to do something
with that error right? What happens if you want to show a spinning indicator
that the call is working. If the server returns a response (on say a fetch or
an index), what do you do with that response?

I think this ([https://github.com/aarondfrancis/vue-model#update-a-
model](https://github.com/aarondfrancis/vue-model#update-a-model)) is a good
example to try to work out with the centralized service approach, and see
which you like better.

Try it for one model, and then think about if you had to do two or three
similar resources.

Hope that makes sense!

~~~
Zenbit_UX
Hmm interesting ideas.

To answer your questions, if http call fails I'm currently handling it in a
.Catch. I haven't done much specific error handling yet, was probably going to
do a bunch of if statements to figure out the nature of the error and then
output relevant messages to toast.

For the loading indicator(s) I was planning on using just one, globally, like
YouTube does - their slim red line that runs a long the top of the viewport?

I'll definitely look more into your library but I'm hesitant to bulk up my app
unless something provides critical functionality. Using Quasar framework right
now and already a little conserned with load times and bulk. Although I
haven't built for production yet with full minification, etc.

Thanks for your time in responding to my stupid questions!

------
softwarelimits
@author: please take a look at [http://feathersjs.com](http://feathersjs.com)
\- your work is very interesting, but you should make it a layer on top of a
modular backend service and feathers would be a good choice, but maybe people
would like to use phoenix or other backends on the server.

Also you may be interested in [http://derbyjs.com/](http://derbyjs.com/) and
do some research about operational transformations.

Ah, and please do not forget that in a real app you need to lock the records
users are editing, but not indefinitely.

Thanks and happy coding!

~~~
softwarelimits
Another interesting read for anyone designing CRUD software in 2017:
[https://ipfs.io/blog/30-js-ipfs-crdts.md](https://ipfs.io/blog/30-js-ipfs-
crdts.md)

------
tudorconstantin
As a backend developer let me tell you this looks so familiar. This is a good
thing because the frontend MVCs did quite clicked for me yet.

------
IgorPartola
What exactly happens when the user modifies the field customer.name but
doesn’t hit save/trigger customer.http.update()? I usually resolve this by
mirroring customer.name to this.formData.name and having a reset property for
this.formData.

Also how does it work with nested models returned from the server? Can I do
user.tickets.push(newTicket) in some form?

~~~
aarondf
If the user modifies customer.name and then doesn't trigger save, nothing
happens, ie: doesn't persist to the backend.

To "reset" a form, I guess you could get it again from the server via
customer.http.fetch()

~~~
IgorPartola
But how do I know when to fetch? With my approach global state is not modified
until the user explicitly does it. Modifying customer.name and not saving it
will persist until the next fetch() and in the meantime the user will see it
as if the name was actually saved.

~~~
aarondf
Not sure I'm following your exact setup... But you could of course just make a
model for your form and _not_ your customer. Without seeing your full setup,
it's hard for me to say for sure!

~~~
IgorPartola
In my apps customer will be a global via VueX. If I go to the Edit Customer
view, change customer.name, but don’t save, the change will be visible
throughout the rest of the app: the customer list, all the other views where
the customer’s name is displayed. It would be a bug to show unsaved data
anywhere but in the edit view. And if I go to the edit view, make changes,
then exit without saving, then go back, the view should be reset. Making two
copies of a global model instance seems like the wrong solution.

Edit: what this is missing is essentially an implementation of forms. That
doesn’t necessarily need to be a part of this project, but it should be used
together with this. Otherwise you end up at least temporarily persisting
changes that were never saved. See the relationship between Django models and
forms.

Also, one feature I have implemented in at least one of my apps has been using
WebSockets to deliver new, updated, and deleted models from the server. That
could be a super powerful addition here.

~~~
DanitaBaires
> And if I go to the edit view, make changes, then exit without saving, then
> go back, the view should be reset.

This is business logic and it's dependent of the UI. If you have a Save button
but the user doens't save and leaves the page, on change route you reset the
model. If it doesn't have an explicit button, on change route you save the
model. It has nothing to do with the framework.

If you mean the library doesn't have a way to reset the model to its original
state, it doesn't take too long to implement, just call fetch as the author
says or when you load the original model store a copy somewhere, even inside
the model itself.

~~~
IgorPartola
I disagree. First, I don’t want to write a “destructor” for every view a model
it touches, because I already have to write a “constructor”. I will reset the
view’s local state when it’s activated and not worry about resetting it when
it is deactivated. Also, my apps are built such that state is shared across
tabs of the app if they are open in the same browser. The user doesn’t have to
navigate away to see model changes. So your solution would be broken in that
case.

Calling fetch all the time because the user navigated away doesn’t make sense
performance wise.

Think about it. If Django/RoR had their ORM make all modal instances global
singletons and any view there could modify the properties of the model, but
for persistence still had to save them to the DB, would you want all of your
views to have to roll their own reset code? Would you trust such code? No.
That would be nuts. So why is that ok on the client side? Local state is ok
for emphemeral data. Use it instead of modifying global state with data that
is not ready to be committed.

------
enraged_camel
Nice. Will give it a whirl in my next project.

Question for the author: is this "production-ready", or is it a toy project?
If I start using this, do I have to worry about you abandoning the project six
months down the line as you become interested in other things?

That in my mind is the biggest issue with open-source in general: people
release stuff into the wild but don't follow through.

~~~
aarondf
Would it have been better if I'd never released it?

This free software comes with no guarantees either explicit or implied.

That being said, I use it all the time so I plan on maintaining it. You are
also welcome to contribute if you find something that needs fixing

------
archseer
I like this, but I feel that a repository pattern would be better suited
(Repo.update(model) vs model.update(), model being ).

------
hellovue
Laravel Spark has similar functionality without the need for a plugin which
makes it more reusable.

[https://spark.laravel.com/docs/4.0/forms](https://spark.laravel.com/docs/4.0/forms)

~~~
s4vi0r
Yeah, but then you're writing PHP :^)

------
ElatedOwl
Neat! Is this meant to replace Vuex / some other store?

~~~
aarondf
No, I probably wouldn't say that this replaces Vuex.

I don't use Vuex, I find it to be more complex than I need. I build a lot of
CRUDdy apps that are pretty straightforward, so I've never dived into it much.
I've watched the lessons on Laracasts for it, but just don't need it yet.

This is to replace the instances where you have a update() methods in your vue
instances that set up axios, organize the data, send the request, and handle
the response.

It's very much for restful resources.

------
28inch
I like it but the naming is rather weird. 'vue model' is heavily used in the
vueniverse.

------
lucisferre
Ah the legacy of Rails. Active Record anti-patterns for everything.

~~~
aarondf
Thanks for checking it out! I put a lot of work into it so it's nice to know
you looked at it.

~~~
lucisferre
There are many better ways to manage front end state that don't actively
encourage violating SOLID principles, SRP and OCP in particular. Obviously no
one cares about SOLID anymore.

~~~
aarondf
Thanks for continuing to take such an interest in this! It seems like you
don't like and won't ever use the project, which is perfectly ok with me.

~~~
lucisferre
Yes I do take an interest in helping others avoid mistakes I've made in the
past. It isn't really an unreasonable thing to do. I'm actually sorry that it
appears this won't be of help to you.

~~~
Can_Not
Maybe you could talk more about better solutions instead of vague handy-wavey
bashing?

