pushtorefresh / storio

Reactive API for SQLiteDatabase and ContentResolver.
Apache License 2.0
2.55k stars 182 forks source link

Relations example #436

Closed TheRishka closed 9 years ago

TheRishka commented 9 years ago

Could you please add some more complex sample-app with an example of making many-to-many relations? I've implemented storIO in my project quite successfully but now i'm stuck at creating more complex databases. I understand that it refers more to SQLite, but your vision on resolving this would be much helpful. Thank you!

artem-zinnatullin commented 9 years ago

Sure, you are second person who asks this, I'd work on it today/tomorrow.

Rainer-Lang commented 9 years ago

Please also add me.

fdorssers commented 9 years ago

I'm also looking forward to seeing how you would implement this using StorIO. For example adding an author class to your current sample and having multiple tweets refer to the same author.

artem-zinnatullin commented 9 years ago

I'd make it like this:

// Pseudo code

class User {
  String email;
}

class Tweet {
  User user;
  String content;
}

class TweetGetResolver extends GetResolver  {
  @Override @NonNull public Tweet mapFromCursor(@NonNull Cursor cursor) {
    String userName = cursor.getString(cursor.getColumnIndex("user"));
    String content = cursor.getString(cursor.getColumnIndex("content"));
    User user = …; // get user by name
    return Tweet.newInstance(user, content);
  }
}
Rainer-Lang commented 9 years ago

Please also lists. I'm thinking about using StorIO instead of an orm-lib.

dimsuz commented 9 years ago

@artem-zinnatullin in your example above, what should I put instead of "//get user by name"? For example if User is stored in another table. can I use storIO instance to perform another blocking call here? Is it good to do this inside a Resolver? Or is this the wrong approach and "raw query" + "join" should be used instead?

artem-zinnatullin commented 9 years ago

cc all interested in this issue: take a look at #494, it's my vision of relations with StorIO.

Feel free to ask any questions here.

@dimsuz

In your example above, what should I put instead of "//get user by name"? For example if User is stored in another table. can I use storIO instance to perform another blocking call here?

Yes, feel free to use StorIO methods to do what you need to do (get other objects and so on, see PR #494), of course you can use raw queries too.

The only thing is that StorIO operations will create useless notification… I think we will eliminate them somehow later, it's not very critical.

dimsuz commented 9 years ago

@artem-zinnatullin sadly, I cannot reuse storIO object in mapFromCursor and this means that all my relation-based 'get' queries must be rewritten to raw queries with JOIN which kinda defeats the whole purpose of fluent API which Query brings us - and now I need to have all those DAO-like classes again which would hide all those raw queries behind nicer interfaces like Relations.java in your example. Imagine I also need that JOIN but with some additional 'where' - that's one more method in that class, then more and more will come :)

artem-zinnatullin commented 9 years ago

@dimsuz why you can not reuse StorIO methods in mapFromCursor()? You can make custom query but reuse type-mapping :)

dimsuz commented 9 years ago

mapFromCursor() doesn't have storIO argument passed in. It gets passed only to other methods of Resolver class (e.g. performGet). So I could reuse it in resolver's mapFromCursor() only by overriding performGet and saving it as field. But judging from API it is not clear whether it is safe to do so or not. It is passed by argument to limited set of methods contrary to having some getStoreIO() method => so by looking at API alone I would assume I shouldn't store it :)

artem-zinnatullin commented 9 years ago

I'll add more javadoc for operation resolvers. Feel free to use most abstract operation resolvers, we've added default implementations to save users from boilerplate :)

dimsuz commented 9 years ago

So you are saying that the way to do this would be to store reference in resolver field and use it in mapFromCursor?

artem-zinnatullin commented 9 years ago

So you are saying that the way to do this would be to store reference in resolver field and use it in mapFromCursor?

Nope, it's not a good solution, because potentially you can use same resolver for different StorIO instances, so I would not depend on this.

You can extend GetResolver, or just pass instances of GetResolvers of required classes.

Like this:

public class TweetWithUserGetResolver extends DefaultGetResolver<TweetWithUser> {

    @NonNull
    private final TweetGetResolver tweetGetResolver;

    @NonNull
    private final UserGetResolver userGetResolver;

    public TweetWithUserGetResolver(@NonNull TweetGetResolver tweetGetResolver, @NonNull UserGetResolver userGetResolver) {
        this.tweetGetResolver = tweetGetResolver;
        this.userGetResolver = userGetResolver;
    }

    // We expect that cursor will contain both Tweet and User: SQL JOIN
    @NonNull
    @Override
    public TweetWithUser mapFromCursor(@NonNull Cursor cursor) {
        final Tweet tweet = tweetGetResolver.mapFromCursor(cursor);
        final User user = userGetResolver.mapFromCursor(cursor);

        return new TweetWithUser(tweet, user);
    }
}

Instead of this:

public class TweetWithUserGetResolver extends DefaultGetResolver<TweetWithUser> {

    // We expect that cursor will contain both Tweet and User: SQL JOIN
    @NonNull
    @Override
    public TweetWithUser mapFromCursor(@NonNull Cursor cursor) {
        final Tweet tweet = Tweet.newTweet(
                cursor.getLong(cursor.getColumnIndexOrThrow(TweetsTable.COLUMN_ID)),
                cursor.getString(cursor.getColumnIndexOrThrow(TweetsTable.COLUMN_AUTHOR)),
                cursor.getString(cursor.getColumnIndexOrThrow(TweetsTable.COLUMN_CONTENT))
        );

        final User user = User.newUser(
                cursor.getLong(cursor.getColumnIndexOrThrow(UsersTable.COLUMN_ID)),
                cursor.getString(cursor.getColumnIndexOrThrow(UsersTable.COLUMN_NICK))
        );

        return new TweetWithUser(tweet, user);
    }
}

StorIO is not one of black magic libs/frameworks, just write your code which uses StorIO as your normal code :)

dimsuz commented 9 years ago

Oh, that's a nice hint, thanks! Still feeling a little weird about joins requiring raw query (after wow-effect of other StorIO api), but your are right, this would require some black magic to support using fluent API and some tradeoffs would likely need to be made and this is the thing which everyone is struggling with when using ORMs :)

artem-zinnatullin commented 9 years ago

Still feeling a little weird about joins requiring raw query

It's just because StorIO is not ORM and will never be it (I hope :))

dimsuz commented 9 years ago

:+1: