nested resource urls macro #10
Comments
This would be definitely cool, and I can't say I've come across this in a Rails app. Out of curiosity though, if you have a
As you already have the |
If, for example, you had an |
I see. Another thing - you say you wish to be able to NSRails exposes a lot of useful internal methods, so you'll find that this is quite easy to do. This implementation is from the // User.m
- (NSArray *) allInvites
{
// GET to /users/1/invites
NSMutableArray *returnData = [self remoteGET:@"invites" error:&e];
[returnData translateRemoteDictionariesIntoInstancesOfClass:[UserInvite class]];
return returnData;
}
- (void) createInvite:(UserInvite *)invite
{
NSDictionary *rep = [invite remoteDictionaryRepresentationWrapped:YES];
// POST to /users/1/invites
NSDictionary *dict = [self remoteRequest:@"POST" method:@"invites" body:rep error:&e];
[invite setPropertiesUsingRemoteDictionary:dict];
} Check out the NSRRemoteObject API for more. However, I have another idea you might find interesting. Every //overriding NSRails method
- (id) remoteRequest:(NSString *)verb method:(NSString *)path body:(id)body error:(NSError **)error
{
NSString *newPath = [NSString stringWithFormat:@"users/%@/%@",user.remoteID,path];
return [[self getRelevantConfig] makeRequest:verb requestBody:body route:newPath error:error];
} Or, override this guy (not documented): - (NSString *) routeForInstanceMethod:(NSString *)customRESTMethod
{
NSString *newPath = [NSString stringWithFormat:@"users/%@/invites/%@",user.remoteID,self.remoteID];
if (customRESTMethod)
newPath = [newPath stringByAppendingPathComponent:customRESTMethod];
return newPath;
} And thus: // DELETE to /users/1/invites/3, if userInvite.user.id = 1
[userInvite remoteDestroy:&e]; Using this, though, I also want to remind you about the NSRUseModelName macro that'll make |
I'm realizing now that both the override implementations I gave were "undocumented" - just using private methods. They're fine for a hack now, but I would recommend doing the helper methods as I explained before the break. And now that I'm giving it more thought, you may be right in including this in a macro. I would make it a full path though, in case there are other special behaviors, and so it could even obsolete NSRUseModelName. Something like:
Where ":user" would be interpolated to Again, though, I am curious about how you planned on implementing this with class methods like the "get all", where you wouldn't have a |
So I started writing code for this with a new instance method called New in NSRMacros.h
And using it in a sample class file.
The side effect is that now all of the class methods cannot be class methods since they are affected by the instance's possible necessity for a prefix to the controller route. This departure is too large to just include in a pull request, so i'm wondering what avenues we have if we wanted to incorporate this back into the mainline. |
Class methods could throw an exception if this macro is defined. And I suppose the "remote all" and "create" methods could be implemented as I outlined above. By the way, Now I'm actually thinking of allowing a variadic macro, which could allow for a lot of flexibility. I'm not sure yet, but maybe something like this: NSRUseResourcePath(@"users", user, @"invites");
// means "users/[self.user.remoteID]/invites"
// or "users/[self.user.remoteID]/invites/[self.remoteID]" |
Actually, sorry - just took another look at your macro implementation and it's a lot better than I thought. I didn't even think of using the real instance variable within the macro expansion. Let's go with your way, but just with returning the object. As I said, this makes it easier to write code/debug, especially since
and leaving the logic in the caller. I just wanna point out, too, that this could support several levels of nesting. If you then had a nested class from invite,
This resource would first map |
I've been going back and forth with this one for the last hour because it breaks all of the class methods currently defined. Ugh. I really do like your solution as presented above...where it would be up to the author to define a method like Rails handles this by dynamically adding methods to a class when you specify a has_ or belongs_to relationship. The link below describes how to add methods at runtime for objc, but I haven't the foggiest idea what implications it would have for the project. http://theocacao.com/document.page/327 I'm becoming more comfortable with a middle ground of GET and POST happening as a nested resource and PATCH and DELETE happening on the resource itself. As mentioned above, I'm all about the readability of an API and considering what "verbiage" you're expressing with your network calls.
GET and POST maintain their context of making the request against a user, while PATCH and DELETE happen on the user_invite record invidually. This is pretty and all, but I'm not sure how it helps out the situation. I guess a better way to frame the question is "how do you specify foreign key for a fetch". With the current code it doesn't even look like there's a specific method - you'd have to use remoteRequest:method:body and use the body to specify something like |
Check out the latest commit - I implemented everything we talked about. The way I handled class methods is to simply route them ignoring the NSRUseResourcePrefix declaration (so doing a In terms of specifying foreign key on fetch, I believe that's entirely up to your Rails back-end to decide. If you are willing to nest your object within the URL, your app could easily find what user it associates with via the And if not, that's what the "Method missing" magic is something I've definitely considered but will absolutely not implement :) It's fun to play around with but is too much of a hack in Objective-C and might get Xcode angry when giving it bad selectors. If you're interested though, I've played around extensively with it in a recent project: DHUserDefaults. |
Sorry - I think I misunderstood the question about foreign keys on fetch before. I now see, but why is this an issue if you use In regards to using the traditional construction for DELETE/PATCH and the nested one for GET/POST, that may also be a user setting in NSRConfig. Perhaps even optional parameters to the NSRUseResourcePrefix macro? NSRUseResourcePrefix(user, @"GET", @"POST"); I particularly like that. |
What then is left to do to fetch an association? Not all of the associations are eager-loaded and sent on a fetch from the API.
Your solution above still holds true, issue a
Not sure I like that name per se, but having something in this vein will allow using existing methods on UserInvite (such as masterPluralName) to construct the path. The implementation of Any other ideas I'm open to, but I feel like this is going to be a common-enough idiom that it's worth exploring. UPDATE: cleaned up example code |
I'd turn it around and add a class method (name not binding): + (NSArray *) remoteAllViaObject:(NSRRemoteObject *)obj error:(NSError **)e Then, maybe add a property flag in NSRMap, @implementation User
NSRMap(*, invites:UserInvite -???); that would indicate to use Then, yes, perhaps an additional customized - (BOOL) remoteFetchWithNestedProperties:(BOOL)yn error:(NSError *)error would retrieve any properties declared with this flag using The issue with this is that you don't have precision on which nested properties you'd want to fetch if your class defines multiple. But if this is desired, it'd require manually entering in a property either way, so I don't think hardwiring This also solves the issue in your proposition where Just my first thoughts - let me know what you think. |
Check out this example. I "log in" by creating an authentication token, fetch the user corresponding to the authentication token created (getting my own profile), and then trying to create a user invite for that user. NSError* e = [NSError new];
PSAuthenticationToken* authToken = [PSAuthenticationToken new];
authToken.email = @"scott@scottkle.in";
authToken.password = @"seedpw";
BOOL createSuccess = [authToken remoteCreate:&e];
[NSRConfig defaultConfig].appOAuthToken = authToken.key;
PSUser* u = [PSUser remoteObjectWithID:authToken.user.remoteID error:&e];
PSUserInvite* ui = [PSUserInvite new];
ui.twitterHandle = @"github";
[ui remoteCreate:&e]; And the PSUserInvite header. @implementation PSUserInvite
@synthesize challenge, user, phoneCountry, phoneNumber, email, facebookUserID, twitterHandle, phoneDigest, emailDigest, facebookUserIDDigest, twitterHandleDigest;
NSRMap(*, challenge, user -b, phoneCountry -s, phoneNumber -s, email -s, facebookUserID -s, twitterHandle -s);
NSRUseResourcePrefix(user);
NSRUseModelName(@"invite")
@end In the
ie. a class method that doesn't have access to the prefix. I believe everything else is routed through This example ends up posting to |
Fixed this last night but didn't push sorry |
Closing, as I believe there's as much support for this as needed. I'm still considering adding an NSRMap flag to allow an instance method like user.invites = [UserInvite remoteAllViaObject:user error:&e]; although I guess it's not that bad. I also want to mention here that the NSRUseResourcePrefix macro has been since deprecated. Just override @implementation User
- (NSRRemoteObject *) objectUsedToPrefixRequest:(NSRRequest *)request
{
return user;
}
@end The request is passed in if you want to do it conditionally based on the HTTP method, just return nil. |
nested resources in rails are exclusively accessed with the nested url (at least in my case?) and i'm struggling to figure out how to make this addition with NSRails. for example...
Invites then are accessed in relation to some user.
At a cursory glance this would be handled by some macro with syntax recognition for an embedded property - example shown as
NSRNestedResourcePrefix
below. Let's pretend we have aUser
model and aUserInvite
model to correspond to the above example.Thoughts are appreciated.
The text was updated successfully, but these errors were encountered: