Hacker News new | comments | show | ask | jobs | submit login
Show HN: Vue-Model – Models with HTTP Actions for Vue.js (github.com)
109 points by aarondf 98 days ago | hide | past | web | favorite | 44 comments

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!

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.

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.

Thanks for saying that. It sounds like you and I are quite similar. I've never built a full SPA because I it's _so_ much easier and faster for me to build "traditional" apps. Since discovering Vue.js I've started wading further and further into sprinkling JS components onto the page.

Vuex, Redux, etc just don't make sense in my brain, so I've never used them. I built this because I wanted something simple to interact with my Laravel backends. Most of what I do is CRUDdy, and this is super helpful for that.

If you’re looking for something more Django flavored for your spa, there’s always Ember[0]

[0] https://emberjs.com/

You might wanna look into EmberCLI. It's not as trendy or popular as Vue or React (rendering speed is a big reason), but it might be what you're looking for.

Might be the first time I've seen the words "elegant", "simple" and "Django's ORM" in the same sentence. You could say it's easy to get started with, or do simple queries, but it's definitely not simple nor elegant.

Haha, to each their own. There is such a thing as the object-relational impedance mismatch for a reason. I'm currently learning Elixir and Phoenix and it's ORM Ecto has some really interesting ideas. Functional programming seems to map to relational databases better than object oriented does.

Ecto is really interesting, but it's not an actual ORM[0], Elixir doesn't deal with objects, there's a free book on ecto that's pretty interesting:


Oh, I guess that's true in the sense that it doesn't map to objects but it accomplishes the same goal in a functional style. Maybe call it a Struct-Relational Mapper.

Yup, Ecto is awesome. For a project this recent, it does a lot of things right and gets out of your way when you need plain sql.

I've been using Django for a decade and there are simple queries that were impossible with the ORM until recently.

If you have to twist the ORM into spitting out the SQL you already know how to build, you know there's something wrong.

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?

> 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) 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!

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!

@author: please take a look at 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/ 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!

Neither of these things look like directions I want to go in. Thanks for the suggestions though!

Another interesting read for anyone designing CRUD software in 2017: https://ipfs.io/blog/30-js-ipfs-crdts.md

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.

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?

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()

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.

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!

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.

> 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.

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.

@IgorPartola, it sounds like this may not be the right fit for your setup

No, this is not about his setup, Igor is giving you very valuable insights about important features that might be very good addition to your project.

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.

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

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

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


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

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

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.

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

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

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

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.

For those of us who have no clue what you're talking about, could you please take the time to explain what you mean? For example, what are the better ways to manage front-end state? How does this project violate the single-responsibility and open/closed object-oriented design recommendations? Is there anything the author could do to address your concerns?

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.

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.

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

If stuff like handling validation errors properly() is part of the Rails legacy then I would happily embrace it.

() I'm not seing that a lot from the frontend people here.

You happily embrace violations of single responsibility?

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | DMCA | Apply to YC | Contact