ufront / ufront-mvc

The core MVC framework that powers ufront
MIT License
17 stars 15 forks source link

Need a way to fetch HasOne/BelongsTo fields from client-side #18

Closed kevinresol closed 8 years ago

kevinresol commented 9 years ago
// Model:
class User extends Object
{
  public var profile:HasOne<UserProfile>;
}

class UserProfile extends Object
{
  public var user:BelongsTo<User>;
  public var name:SString<255>;
}

// Controller:
@:route(GET, "/")
public function main()
{
  return asyncApi.getUser().map(function(user) return new ViewResult({user:user});
}
<!-- layout.html (erazor engine) -->
@user.profile.name

The above code works at server side but not only client side because profile is a getter function but not real property thus not included in the serialized string sent to the client. (so user.profile is null at client side)

I tried to hack into DBMacros.hx and Object.hx to make it serialize HasOne & BelongsTo as well. But that seems to lead to a circular reference (stack overflow) because User HasOne Profile BelongsTo User HasOne Profile BelongsTo User.......

kevinresol commented 9 years ago

Is it possible to automatically fetch the profile field (through some remoting behind the hood) when the templating engine needs it? And only when all the required fields are ready, it does the actual rendering.

kevinresol commented 9 years ago

Another idea, is it possible to keep a global cache of the database objects?

For example, currently when user.profile or profile.user is accessed, under the hood it fetches a the corresponding object using manager.get() or select() and it is cached in the object (which does the fetching) itself. So it will cause circular reference when HasOne and BelongsTo are also serialized. (i.e. user.profile creates a new profile object and profile.user creates a new user object which in turn creates a new profile object again) The problem could possibly be solved if we can keep a global cache of the User and Profile objects so Serializer.USE_CACHE can handle them without causing circular reference?

jasononeil commented 8 years ago

So I've been coming back to this issue.

The global cache idea is what I was trying to do with ufront-clientds, but I'm not using it anymore - it was kind of cool in theory, but in practice code got very very messy very quickly. Each request for an object is asynchronous, so returns a Future (in fact, clientds used the HxPromise library instead). This led not only to callback hell, but also to confusion: should user.groups be cached synchronously, or should it be a property that returns a Future? At the job where I used that we ended up moving away from using it wherever possible.

The better solution, that I'm suggesting now, is that we use UFApi remoting, but have an easy way to specify if relationships should be included in the serialization.

The first step I've just implemented in Ufront-ORM

userProfile.hxSerializationFields.push( "user" ); // Include a BelongsTo<User> property
userProfile.user.hxSerializationFields = ["username","groups"]; // Include the ManyToMany<User,Group>
return userProfile;

In future I'll attempt a macro like...

return BlogPost.manager.all().alsoWithFields([ author, author.name, author.id, tags[].name ]);
return BlogPost.manager.all().onlyWithFields([ title, url, author.name, author.id, tags[].name ]);
return User.manager.all().withoutFields([ password, salt ]);

...that is a type safe way of modifying hxSerializationFields on the relevant objects so you can easily pick which fields to return in your API.

Thoughts?

kevinresol commented 8 years ago

I didn't used ufront-orm for some time because I am using nodejs + mongoose at the moment. So I don't really have much comment on this right now, please feel free to modify if you see it appropriate.

jasononeil commented 8 years ago

Okay, no worries :) I'll close this issue and track progress at:

https://github.com/ufront/ufront-orm/issues/25