In my CoreData model I have an Entity Article with 20 properties, and a child entity Variant, with a to-one relationship "master" to Article. So Variant has all attributes of Article parent class, but I want that if an attribute is nil, the getter should return the one of the master Article.
In code:
- (NSString *)getSomeAttribute {
NSString *tmp = self.someAttribute;
if (tmp == nil)
tmp = self.master.someAttribute;
return tmp;
}
I don't want to write 20 getters like this, is there a way to write a "general" getter for all attributes at once?
You can't skip the getters, because if you don't implement them, Core Data will generate them. What you can do is factor all of the custom code into one core method and have your other getters call that. You still end up with a bunch of getters, but they're one-liners. Something like this (warning, typed into a web browser and not compiled):
- (id)valueForKeyFromSelfOrMaster:(NSString *)key
{
[self willAccessValueForKey:key];
id value = [self primitiveValueForKey:key];
[self didAccessValueForKey:key];
if (value == nil) {
value = [self.master valueForKey:key];
}
return value;
}
- (NSString *)someAttribute
{
return [self valueForKeyFromSelfOrMaster:#"someAttribute"];
}
Related
I'm developing an application that have views with a lot of fields. I have been manipulating them one by one because I don't know how to reference class attributes to be able to dynamically save them.
Right now I have a lot of line of codes to manipulate a single field... For example:
self.rowField.delegate = self;
NSArray *collectionNames = #[#"personTypeCategories", #"personTypes", #"genders", #"rows", #"seats", #"seatingOthers", #"restraintSystems", #"helmetUses"];
if ([collectionName isEqualToString:#"personTypeCategories"]) {
[self loadDefaultForCollection:#"personTypeCategories" toField:self.personTypeCategoryField withKey:#"PersonTypeCategoryID" defaultValue:self.editingPerson.typeCategoryKey];
}
if (textField == self.rowField) {
[self showCollection:#"rows" withIDColumn:#"RowID" withField:textField];
return NO;
}
When I have a new field that follow our standard of saving information and later posting it to the server I need to create this lines for each one and now I want to do it dynamically by creating an array like this:
- (void)loadViewConfiguration {
self.viewElements = [[NSMutableArray alloc] init];
[self.viewElements addObject:#{
#"restMethod" : #"personTypeCategories",
#"key" : #"PersonTypeCategoryID",
#"field" : self.personTypeCategoryField,
#"enabled" : #YES,
#"modelAttr" : &self.editingPerson.typeCategoryKey
}];
}
The problem is in the modelAttr, I need to have that class attribute reference in order to later go and modify or get it.
Another option could be if I can dynamically call class attributes like you can do in PHP, for example:
$attr = "name";
$editingPerson->{$attr} = "Omar";
Thanks for the help.
I didn't know you can use key-value coding for this purpose.
To accomplish this what I did is to simply save the name of the attribute and later get the value by using:
[classInstance valueForKey:elem[#"modelAttr"]];
An example of this:
for (NSDictionary *elem in self.viewElements) {
if ([collectionName isEqualToString:elem[#"restMethod"]]) {
[self loadDefaultForCollection:elem[#"restMethod"] toField:elem[#"field"] withKey:elem[#"key"] defaultValue:[self.editingPerson valueForKey:elem[#"modelAttr"]]];
}
}
Thanks for the advice by #matt
main principles:
object could be created through class method by providing unique identifier (whatever)
if object with given identifier doesn't exists, returned new object otherwise returned existing one
class guarantees that ONLY ONE object with given identifier could exist (sort of internal singleton)
So main point in keeping objects with unique filed (f.e id) for future using, since they might have own states (f.e loading, loaded so on) we are allowed to use it everywhere we need it without re-creating.
Is it design pattern?
F.e:
Advirtisement.h
#interface Advertisment : NSObject
+ (instancetype)adWithID:(NSString *)adID;
+ (NSMutableArray *)sharedAds;
Advertisement.m
+ (instancetype)adWithID:(NSString *)adID {
NSMutableArray *ads = [[self class] sharedAds];
// Look for existing ad based on the id
Advertisement *returnableAd = nil;
for (Advertisement *currentAd in ads) {
if ([currentAd.adID isEqualToString:adID]) {
returnableAd = currentAd;
break;
}
}
// Create a new ad instance for this id if one doesn't already exist.
if (!returnableAd) {
returnableAd = [[[self class] alloc] initWithID:adID];
[ads addObject:returnableAd];
}
return returnableAd;
}
}
+ (NSMutableArray *)sharedAds
{
static NSMutableArray *sharedAds;
if (!sharedAds) {
sharedAds = [NSMutableArray array];
}
return sharedAds;
}
It's Lazy Initialization - and some would also consider it an instance of a Factory Pattern (others would argue that in itself a factory doesn't constitute a pattern)
How can I use Github Mantle to choose a property class based on another property in the same class? (or in the worse case another part of the JSON object).
For example if i have an object like this:
{
"content": {"mention_text": "some text"},
"created_at": 1411750819000,
"id": 600,
"type": "mention"
}
I want to make a transformer like this:
+(NSValueTransformer *)contentJSONTransformer {
return [MTLValueTransformer transformerWithBlock:^id(NSDictionary* contentDict) {
return [MTLJSONAdapter modelOfClass:ETMentionActivityContent.class fromJSONDictionary:contentDict error:nil];
}];
}
But the dictionary passed to the transformer only includes the 'content' piece of the JSON, so I don't have access to the 'type' field. Is there anyway to access the rest of the object? Or somehow base the model class of 'content' on the 'type'?
I have previously been forced to do hack solutions like this:
+(NSValueTransformer *)contentJSONTransformer {
return [MTLValueTransformer transformerWithBlock:^id(NSDictionary* contentDict) {
if (contentDict[#"mention_text"]) {
return [MTLJSONAdapter modelOfClass:ETMentionActivityContent.class fromJSONDictionary:contentDict error:nil];
} else {
return [MTLJSONAdapter modelOfClass:ETActivityContent.class fromJSONDictionary:contentDict error:nil];
}
}];
}
You can pass the type information by modifying the JSONKeyPathsByPropertyKey method:
+ (NSDictionary *)JSONKeyPathsByPropertyKey
{
return #{
NSStringFromSelector(#selector(content)) : #[ #"type", #"content" ],
};
}
Then in contentJSONTransformer, you can access the "type" property:
+ (NSValueTransformer *)contentJSONTransformer
{
return [MTLValueTransformer ...
...
NSString *type = value[#"type"];
id content = value[#"content"];
];
}
I've had a similar issue, and I suspect my solution isn't much better than yours.
I have a common base class for my Mantle objects, and after each is constructed, I call a configure method to give them chance to set up properties that are dependent on more than one "base" (== JSON) property.
Like this:
+(id)entityWithDictionary:(NSDictionary*)dictionary {
NSError* error = nil;
Class derivedClass = [self classWithDictionary:dictionary];
NSAssert(derivedClass,#"entityWithDictionary failed to make derivedClass");
HVEntity* entity = [MTLJSONAdapter modelOfClass:derivedClass fromJSONDictionary:dictionary error:&error];
NSAssert(entity,#"entityWithDictionary failed to make object");
entity.raw = dictionary;
[entity configureWithDictionary:dictionary]; // Give the new entity a chance to set up derived properties
return entity;
}
I have a question about CoreData and NSManagedObject.
In the architecture of my application I have a REST client That builds instances of objects That I receive from the server.
For example I have this method:
- (NSArray*) getAllCards:(NSDictionary*) jsonResponse;
That takes the JSON response and returns an array of Cards.
It's ok ...
Now I need to save my Card in CoreData.
I defined my model so I have some subclass of NSManagedObject.
For example: Card: NSManagedObject
but I would like to have a simple constructor to construct the object from the JSON and then pass this to my manager who will add it in CoreData.
I would not pass some info related to CoreData (eg. Context ..) to my Client REST ... I would like to have the logic in my CoreData CoreData Manager ..
How can I define a valid constructor of my Cards?
Can I define a constructor in this way?
- (id) initWithName:(NSString*) name
cardId:(NSString*) cardId
type:(NSString*) type
{
self = [super initWithEntity:nil insertIntoManagedObjectContext:nil]; //This is wrong..
if (self) {
self.name = name;
self.cardId = cardId;
self.type = type;
}
}
I see no need to override the init methods. Just give your CoreDataManager class a method that takes the dictionary and generates the core data entities.
-(void)storeCardsFromJSON:(NSDictionary*)dictionary {
for (NSDictionary *cardInfo in dictionary[#"cardArray"]) {
Card *newCard = [NSEntityDescription insert...];
newCard.cardID = cardInfo[#"cardID"];
// etc.
}
// save
}
Optionally, return an array of newly created cards. In this method you could also filter out the cards that already exist (e.g. based on the cardID attribute) and modify those instead of inserting them again.
In your app, you should only use the Card objects from Core Data. There is no need to have a different class of objects, or use NSDictionary objects.
Maybe it will help: for all of my NSManagedObject subclasses, that can be created from JSON, I added a category with names like MyObject+JSON and I have 2 methods there:
+ (instancetype)deserializeFromDictionary:(NSDictionary *)dictionary inContext:(NSManagedObjectContext *)context;
- (void)updateFromDictionary:(NSDictionary *)dictionary;
I use inContext: part because I usually insert objects in batches using private context for the whole transaction.
Methods look like this:
+ (instancetype)deserializeFromDictionary:(NSDictionary *)dictionary inContext:(NSManagedObjectContext *)context;
{
if (!dictionary || ![dictionary isKindOfClass:[NSDictionary class]] || !context) {
return nil;
}
MyObject *anObject = (MyObject *)[NSEntityDescription insertNewObjectForEntityForName:#"MyObject" inManagedObjectContext:context];
[anObject updateFromDictionary:dictionary];
return anObject;
}
And
- (void)updateFromDictionary:(NSDictionary *)dictionary
{
NSParameterAssert(dictionary);
if (!dictionary || ![dictionary isKindOfClass:[NSDictionary class]]) {
return;
}
self.identifier = dictionary[#"id"];
self.title = dictionary[#"title"]; //and so on
}
While using Mantle, is there a possibility to before returning the object we are creating (on this case via JSON) to verify that X and Y properties are not nil?
Imagine this class:
#interface Person : MTLModel <MTLJSONSerializing>
#property(nonatomic,strong,readonly)NSString *name;
#property(nonatomic,strong,readonly)NSString *age;
#end
I want a way to verify that if the JSON I received doesn't have the name (for some reason there was an issue on server's DB) I will return a nil Person, as it doesn't make sense to create that object without that property set.
Whilst you could override the initialiser. It seems more concise to override the validate: as this is called at the last stage before Mantle returns the deserialised object. Makes sense to put all your validation logic in a validate method...
See the final line of the MTLJSONAdapter
id model = [self.modelClass modelWithDictionary:dictionaryValue error:error];
return [model validate:error] ? model : nil;
This tells us that if our custom model returns NO from validate, then Mantle will discard the object.
So you could in your subclass simply perform the following:
- (BOOL)validate:(NSError **)error {
return [super validate:error] && self.name.length > 0;
}
Ideally in your own implementation you probably want to return an appropriate error.
The validate method will then call Foundation's validateValue:forKey:error: for every property that you registered with Mantle in JSONKeyPathsByPropertyKey. So if you want a more controlled validation setup you could also validate your data here..
You can use the MTLJSONSerializing protocol method classForParsingJSONDictionary: to return nil rather than an invalid object:
// In your MTLModelSubclass.m
//
+ (Class)classForParsingJSONDictionary:(NSDictionary *)JSONDictionary {
if (JSONDictionary[#"name"] == nil || JSONDictionary[#"age"] == nil) {
return nil;
}
return self.class;
}
In fact I don't use Mantle, but for validation I use another GitHub Library called RPJSONValidator
It tells you the type that you expect and what type the value has been arrived.
A simple example code
NSError *error;
[RPJSONValidator validateValuesFrom:json
withRequirements:#{
#"phoneNumber" : [RPValidatorPredicate.isString lengthIsGreaterThanOrEqualTo:#7],
#"name" : RPValidatorPredicate.isString,
#"age" : RPValidatorPredicate.isNumber.isOptional,
#"weight" : RPValidatorPredicate.isString,
#"ssn" : RPValidatorPredicate.isNull,
#"height" : RPValidatorPredicate.isString,
#"children" : RPValidatorPredicate.isArray,
#"parents" : [RPValidatorPredicate.isArray lengthIsGreaterThan:#1]
} error:&error];
if(error) {
NSLog(#"%#", [RPJSONValidator prettyStringGivenRPJSONValidatorError:error]);
} else {
NSLog(#"Woohoo, no errors!");
}
Each key-value pair describes requirements for each JSON value. For example, the key-value pair #"name" : RPValidatorPredicate.isString will place a requirement on the JSON value with key "name" to be an NSString. We can also chain requirements. For example, #"age" : RPValidatorPredicate.isNumber.isOptional will place a requirement on the value of "age" to be an NSNumber, but only if it exists in the JSON.
I use a very old version of Mantle. YMMV
You can override the [MTLModel modelWithExternalRepresentation] selector. Make sure to call [super modelWithExternalRepresentation] and then add your own code to check for validate data.
I followed a small issue opened on Mantle:
- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError *__autoreleasing *)error
{
BOOL isValid = NO;
if (self = [super initWithDictionary:dictionaryValue error:error])
{
isValid = ...
}
return isValid?self:nil;
}
So in the end just override:
- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError *__autoreleasing *)error