Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
5528733B16FD62E0006AFD47 /* MMAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 55F951EF165C73460060851E /* MMAppDelegate.m */; };
5569077B16FA750D0040D191 /* MMRecordMarshaler.h in Resources */ = {isa = PBXBuildFile; fileRef = 5569077916FA750D0040D191 /* MMRecordMarshaler.h */; };
5573C68C16FA874600B82CE5 /* MMRecordMarshaler.m in Sources */ = {isa = PBXBuildFile; fileRef = 5569077A16FA750D0040D191 /* MMRecordMarshaler.m */; };
55AA517018BF997F00F54E80 /* CoverImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 55AA516F18BF997F00F54E80 /* CoverImage.m */; };
55E6987E16A247A400F25C9A /* Source.m in Sources */ = {isa = PBXBuildFile; fileRef = 55E6987D16A247A400F25C9A /* Source.m */; };
55E712BB16FE15C900EC1159 /* ADNServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 55E712BA16FE15C900EC1159 /* ADNServer.m */; };
55E712BE16FE15E300EC1159 /* ADNPageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 55E712BD16FE15E300EC1159 /* ADNPageManager.m */; };
Expand Down Expand Up @@ -105,6 +106,8 @@
55211F6916F179E300729C51 /* MMRecordRepresentation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMRecordRepresentation.m; sourceTree = "<group>"; };
5569077916FA750D0040D191 /* MMRecordMarshaler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MMRecordMarshaler.h; sourceTree = "<group>"; };
5569077A16FA750D0040D191 /* MMRecordMarshaler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MMRecordMarshaler.m; sourceTree = "<group>"; };
55AA516E18BF997F00F54E80 /* CoverImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CoverImage.h; sourceTree = "<group>"; };
55AA516F18BF997F00F54E80 /* CoverImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CoverImage.m; sourceTree = "<group>"; };
55E6987C16A247A400F25C9A /* Source.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Source.h; sourceTree = "<group>"; };
55E6987D16A247A400F25C9A /* Source.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Source.m; sourceTree = "<group>"; };
55E712B916FE15C900EC1159 /* ADNServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADNServer.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -261,6 +264,8 @@
children = (
5509C0D216FE5CFD00310806 /* Counts.h */,
5509C0D316FE5CFD00310806 /* Counts.m */,
55AA516E18BF997F00F54E80 /* CoverImage.h */,
55AA516F18BF997F00F54E80 /* CoverImage.m */,
55F95225165C76370060851E /* Post.h */,
55F95226165C76370060851E /* Post.m */,
55E6987C16A247A400F25C9A /* Source.h */,
Expand Down Expand Up @@ -559,6 +564,7 @@
55F95224165C76370060851E /* User.m in Sources */,
55F95227165C76370060851E /* Post.m in Sources */,
55F9522A165C76920060851E /* ADNRecord.m in Sources */,
55AA517018BF997F00F54E80 /* CoverImage.m in Sources */,
55F95243165C77380060851E /* AFHTTPClient.m in Sources */,
55F95244165C77380060851E /* AFHTTPRequestOperation.m in Sources */,
55F95245165C77380060851E /* AFImageRequestOperation.m in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#import "Post.h"
#import "PostCell.h"
#import "ADNPostManager.h"
#import "Counts.h"

@interface ADNPostsViewController ()

Expand All @@ -28,10 +29,13 @@ - (void)viewDidLoad {
[super viewDidLoad];

self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:@selector(getMoreRecentPosts) forControlEvents:UIControlEventValueChanged];
[self.refreshControl addTarget:self
action:@selector(getMoreRecentPosts)
forControlEvents:UIControlEventValueChanged];
[self setRefreshControl:self.refreshControl];

[self.tableView registerNib:[UINib nibWithNibName:@"PostCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"PostCell"];
[self.tableView registerNib:[UINib nibWithNibName:@"PostCell" bundle:[NSBundle mainBundle]]
forCellReuseIdentifier:@"PostCell"];

[self getPosts];
}
Expand All @@ -41,6 +45,45 @@ - (void)viewDidLoad {

- (void)getPosts {
NSManagedObjectContext *context = [[MMDataManager sharedDataManager] managedObjectContext];

MMRecordOptions *options = [Post defaultOptions];

options.entityPrimaryKeyInjectionBlock = ^id(NSEntityDescription *entity,
NSDictionary *dictionary,
MMRecordProtoRecord *parentProtoRecord){
if ([[entity name] isEqualToString:@"CoverImage"]) {
if ([[parentProtoRecord.entity name] isEqualToString:@"User"]) {
if (parentProtoRecord.primaryKeyValue != nil) {
return parentProtoRecord.primaryKeyValue;
}
}
}

return nil;
};

options.recordPrePopulationBlock = ^(MMRecordProtoRecord *protoRecord){
if (protoRecord.primaryKeyValue) {
NSLog(@"Entity: %@ Value Key: %@", protoRecord.entity.name, protoRecord.primaryKeyValue);
} else {
NSLog(@"Entity: %@ Relationship Key: %@", protoRecord.entity.name, protoRecord.relationshipPrimaryKeyProto.primaryKeyValue);
}
};

options.deleteOrphanedRecordBlock = ^BOOL(MMRecord *orphan,
NSArray *populatedRecords,
id responseObject,
BOOL *stop) {
if ([orphan isKindOfClass:[Counts class]]) {
if ([(Counts *)orphan user] == nil) {
return YES;
}
}

return NO;
};

[Post setOptions:options];

[Post
getStreamPostsWithContext:context
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="2061" systemVersion="12D78" minimumToolsVersion="Xcode 4.3" macOSVersion="Automatic" iOSVersion="Automatic">
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="3401" systemVersion="13C64" minimumToolsVersion="Xcode 4.3" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="Counts" representedClassName="Counts" syncable="YES">
<attribute name="followers" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
<attribute name="following" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
Expand All @@ -10,6 +10,16 @@
<entry key="MMRecordEntityPrimaryAttributeKey" value="user"/>
</userInfo>
</entity>
<entity name="CoverImage" representedClassName="CoverImage" syncable="YES">
<attribute name="height" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
<attribute name="url" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="width" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="User" inverseName="coverImage" inverseEntity="User" syncable="YES"/>
<userInfo>
<entry key="MMRecordEntityPrimaryAttributeKey" value="id"/>
</userInfo>
</entity>
<entity name="Post" representedClassName="Post" syncable="YES">
<attribute name="date" optional="YES" attributeType="Date" syncable="YES">
<userInfo>
Expand Down Expand Up @@ -52,15 +62,21 @@
<attribute name="id" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="counts" optional="YES" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="Counts" inverseName="user" inverseEntity="Counts" syncable="YES"/>
<relationship name="coverImage" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CoverImage" inverseName="user" inverseEntity="CoverImage" syncable="YES">
<userInfo>
<entry key="MMRecordAttributeAlternateNameKey" value="cover_image"/>
</userInfo>
</relationship>
<relationship name="posts" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Post" inverseName="user" inverseEntity="Post" syncable="YES"/>
<userInfo>
<entry key="MMRecordEntityPrimaryAttributeKey" value="id"/>
</userInfo>
</entity>
<elements>
<element name="Post" positionX="160" positionY="192" width="128" height="135"/>
<element name="Source" positionX="160" positionY="192" width="128" height="105"/>
<element name="User" positionX="160" positionY="192" width="128" height="135"/>
<element name="Counts" positionX="160" positionY="192" width="128" height="120"/>
<element name="Counts" positionX="0" positionY="0" width="0" height="0"/>
<element name="Post" positionX="0" positionY="0" width="0" height="0"/>
<element name="Source" positionX="0" positionY="0" width="0" height="0"/>
<element name="User" positionX="0" positionY="0" width="0" height="0"/>
<element name="CoverImage" positionX="0" positionY="0" width="0" height="0"/>
</elements>
</model>
24 changes: 24 additions & 0 deletions Examples/MMRecordAppDotNet/MMRecordAppDotNet/CoverImage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// CoverImage.h
// MMRecordAppDotNet
//
// Created by Conrad Stoll on 2/27/14.
// Copyright (c) 2014 Mutual Mobile. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

#import "ADNRecord.h"

@class User;

@interface CoverImage : ADNRecord

@property (nonatomic, retain) NSString * url;
@property (nonatomic, retain) NSNumber * height;
@property (nonatomic, retain) NSNumber * width;
@property (nonatomic, retain) NSNumber * id;
@property (nonatomic, retain) User *user;

@end
21 changes: 21 additions & 0 deletions Examples/MMRecordAppDotNet/MMRecordAppDotNet/CoverImage.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// CoverImage.m
// MMRecordAppDotNet
//
// Created by Conrad Stoll on 2/27/14.
// Copyright (c) 2014 Mutual Mobile. All rights reserved.
//

#import "CoverImage.h"
#import "User.h"


@implementation CoverImage

@dynamic url;
@dynamic height;
@dynamic width;
@dynamic id;
@dynamic user;

@end
2 changes: 2 additions & 0 deletions Examples/MMRecordAppDotNet/MMRecordAppDotNet/MMAppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
//[MMJSONServer registerResourceName:@"posts" forPathComponent:@"posts"];
//[ADNRecord registerServerClass:[MMJSONServer class]];

//[MMRecord setLoggingLevel:MMRecordLoggingLevelDebug];

return YES;
}

Expand Down
55 changes: 55 additions & 0 deletions Source/MMRecord/MMRecord.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

#import "MMRecordErrors.h"
#import "MMRecordLoggers.h"
#import "MMRecordProtoRecord.h"

/** To provide custom parsing functionality for your records, such as the setting a custom key or
establishing a relationship, use the keys below in the UserInfo dictionary of an attribute or
Expand Down Expand Up @@ -569,6 +570,42 @@ typedef BOOL (^MMRecordOptionsDeleteOrphanedRecordBlock)(MMRecord *orphan,
id responseObject,
BOOL *stop);

/**
This block should be used to optionally inject a primary key that will be used to uniquely identify
a record of a given type. The most common use case for this block should be when an API's JSON
response does not contain a record primary key, but the caller who makes the request already has
that key. Generally this will be the root level initial entity that the request to MMRecord is
going to return. This could be used to return primary keys for sub-entities, but this is generally
not recommended.

This block will only be executed if the primary key for a record cannot be obtained from the record
dictionary.

This method can return nil.

@param entity The entity type to evaluate and return a primary key for.
@param dictionary The dictionary being used to populate the given record.
@param parentProtoRecord The parent proto record of the one whose primary key is being evaluated
here. This may be nil if the entity is the initial entity being populated by MMRecord.
@return id The primary key to associate with the record.
*/
typedef id<NSCopying> (^MMRecordOptionsEntityPrimaryKeyInjectionBlock)(NSEntityDescription *entity,
NSDictionary *dictionary,
MMRecordProtoRecord *parentProtoRecord);

/**
This block may be used for inserting custom logic into the record population workflow. This block,
if defined, will be executed prior to the MMRecordMarshaler's -populateProtoRecord: method.

@warning This block should only be used in relatively rare cases. It is not a substitute for proper
model configuration or for marshaler/representation subclassing. It is meant for rare cases where
injecting data into the population flow is required for accurate record population. Because this
block will be executed for each proto record for a given request, performance issues may arrise.
Please use caution.
@param protoRecord The proto record which is about to be populated.
*/
typedef void (^MMRecordOptionsRecordPrePopulationBlock)(MMRecordProtoRecord *protoRecord);


/**
This class represents various user settable options that MMRecord will use when starting requests.
Expand Down Expand Up @@ -648,6 +685,24 @@ typedef BOOL (^MMRecordOptionsDeleteOrphanedRecordBlock)(MMRecord *orphan,
*/
@property (nonatomic, copy) MMRecordOptionsDeleteOrphanedRecordBlock deleteOrphanedRecordBlock;

/**
This option allows you to specify a block that will be executed when a record is populated and no
primary key to identify it is found in the populating record dictionary. This allows you to return
your own primary key that will be used to uniquely identify the record.

@discussion This block should return nil if you have no way to uniquely identify a record for the
given type of entity. The default value of this option is nil.
*/
@property (nonatomic, copy) MMRecordOptionsEntityPrimaryKeyInjectionBlock entityPrimaryKeyInjectionBlock;

/**
This option allows you to specify a block that will be executed immediately before record
population in order to perform some task like inserting data into the population process.

@discussion Default value is nil which means population will be performed normally.
*/
@property (nonatomic, copy) MMRecordOptionsRecordPrePopulationBlock recordPrePopulationBlock;

@end


Expand Down
6 changes: 5 additions & 1 deletion Source/MMRecord/MMRecord.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
#import "MMRecord.h"

#import "MMRecordCache.h"
#import "MMRecordProtoRecord.h"
#import "MMRecordRepresentation.h"
#import "MMRecordResponse.h"
#import "MMServer.h"
Expand Down Expand Up @@ -216,6 +215,8 @@ + (MMRecordOptions *)defaultOptions {
options.keyPathForMetaData = [self keyPathForMetaData];
options.pageManagerClass = [[self server] pageManagerClass];
options.deleteOrphanedRecordBlock = nil;
options.entityPrimaryKeyInjectionBlock = nil;
options.recordPrePopulationBlock = nil;
return options;
}

Expand Down Expand Up @@ -761,6 +762,9 @@ + (NSArray*)recordsFromResponseObject:(id)responseObject
initialEntity:initialEntity
context:context];

response.entityPrimaryKeyInjectionBlock = options.entityPrimaryKeyInjectionBlock;
response.recordPrePopulationBlock = options.recordPrePopulationBlock;

NSArray *records = [response records];

return records;
Expand Down
7 changes: 7 additions & 0 deletions Source/MMRecord/MMRecordMarshaler.m
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@
@implementation MMRecordMarshaler

+ (void)populateProtoRecord:(MMRecordProtoRecord *)protoRecord {
if (protoRecord.primaryAttributeDescription != nil) {
[self setValue:protoRecord.primaryKeyValue
onRecord:protoRecord.record
attribute:protoRecord.primaryAttributeDescription
dateFormatter:protoRecord.representation.dateFormatter];
}

for (NSAttributeDescription *attributeDescription in [protoRecord.representation attributeDescriptions]) {
[self populateProtoRecord:protoRecord
attributeDescription:attributeDescription
Expand Down
4 changes: 4 additions & 0 deletions Source/MMRecord/MMRecordProtoRecord.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@

// Uniquing
@property (nonatomic, strong) id primaryKeyValue;
@property (nonatomic, strong) NSAttributeDescription *primaryAttributeDescription;
@property (nonatomic, weak) MMRecordProtoRecord *relationshipPrimaryKeyProto;
@property (nonatomic) BOOL hasRelationshipPrimarykey;

Expand All @@ -66,6 +67,9 @@
- (void)addRelationshipProto:(MMRecordProtoRecord *)relationshipProto
forRelationshipDescription:(NSRelationshipDescription *)relationshipDescription;

// Returns YES if there is already a valid relationshipProtoRecord for a given relationshipDescription
- (BOOL)canAccomodateAdditionalProtoRecordForRelationshipDescription:(NSRelationshipDescription *)relationshipDescription;

// Returns the proto records for a given relationship description
- (NSArray *)relationshipProtoRecordsForRelationshipDescription:(NSRelationshipDescription *)relationshipDescription;

Expand Down
18 changes: 17 additions & 1 deletion Source/MMRecord/MMRecordProtoRecord.m
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ + (MMRecordProtoRecord *)protoRecordWithDictionary:(NSDictionary *)dictionary
protoRecord.relationshipDescriptionsDictionary = [NSMutableDictionary dictionary];
protoRecord.hasRelationshipPrimarykey = [representation hasRelationshipPrimaryKey];
protoRecord.representation = representation;
protoRecord.primaryAttributeDescription = [representation primaryAttributeDescription];

return protoRecord;
}
Expand Down Expand Up @@ -80,6 +81,17 @@ - (NSArray *)relationshipProtoRecordsForRelationshipDescription:(NSRelationshipD
return relationshipProtoRecords;
}

- (BOOL)canAccomodateAdditionalProtoRecordForRelationshipDescription:(NSRelationshipDescription *)relationshipDescription {
NSString *relationshipName = [relationshipDescription name];
NSMutableOrderedSet *protoSet = [self.relationshipProtosDictionary objectForKey:relationshipName];

if ([relationshipDescription isToMany] == NO && [protoSet count] >= 1) {
return NO;
}

return YES;
}


#pragma mark - Relationships

Expand All @@ -97,7 +109,9 @@ - (void)addRelationshipProto:(MMRecordProtoRecord *)relationshipProto
protoSet = [NSMutableOrderedSet orderedSet];
}

[protoSet addObject:relationshipProto];
if ([self canAccomodateAdditionalProtoRecordForRelationshipDescription:relationshipDescription]) {
[protoSet addObject:relationshipProto];
}

[self.relationshipProtosDictionary setValue:protoSet forKey:relationshipName];
}
Expand All @@ -112,6 +126,8 @@ - (NSString *)description {
return description;
}



- (NSString *)stringForTabLevel:(NSInteger)tabLevel {
NSString *tabs = @"";

Expand Down
Loading