У меня есть приложение для iOS с моделью Core Data, которая имитирует мою внутреннюю модель данных Rails. В моей серверной модели Rails я использую полиморфные ассоциации для нескольких сущностей. Моя модель Rails выглядит примерно так:
Airport < ActiveRecord::Base
has_many :reviews, :as => :reviewable
Restaurant < ActiveRecord::Base
has_many :reviews, :as => :reviewable
Review < ActiveRecord::Base
belongs_to :reviewable, :polymorphic => :true
В моей модели базовых данных у меня есть три отдельных объекта, MyAirport, MyRestaurant и MyReview, с соответствующими свойствами, как указано ниже:
MyAirport
@property (nonatomic, retain) NSSet* reviews; //inverse is airport
MyRestaurant
@property (nonatomic, retain) NSSet* reviews; //inverse is restaurant
MyReview
@property (nonatomic, retain) NSNumber* reviewablId;
@property (nonatomic, retain) NSString* reviewableType;
@property (nonatomic, retain) MyAirport* airport; //inverse is reviews
@property (nonatomic, retain) MyRestaurant* restaurant; //inverse is reviews
Мой вопрос относится к классу MyReview в Core Data. Каков наилучший способ установить правильное отношение базовых данных (например, аэропорт или ресторан) на основе значений в reviewableId и reviewableType?
Я пробовал следующее, и все кажутся немного нечистыми по той или иной причине. Вообще говоря, мне нужно убедиться, что у меня есть ОБА и ревизионный доступ, и доступный для просмотра вид, прежде чем устанавливать правильную связь, что, по-видимому, является основной проблемой, связанной с предполагаемой чистотой. При гидратации этих объектов модели из JSON я на самом деле не контролирую порядок заполнения reviewableId и reviewableType для нового объекта MyReview.
1) Пользовательские сеттеры для одного или обоих reviewableId и reviewableType - я получаю логику в каждом методе сеттера, который проверяет значение другого свойства, чтобы убедиться, что оно заполнено, прежде чем я смог установить отношения аэропорт или ресторан в модели MyReview объект. Вообще говоря, это работает, но это не так, но в каждом сеттере есть какой-то уродливый дубликат кода.
- (void)setReviewableId:(NSNumber*)reviewableId {
[self willChangeValueForKey:@"reviewableId"];
[self setPrimitiveReviewableId:reviewableId];
[self didChangeValueForKey:@"reviewableId"];
if (reviewableId && [reviewableId intValue] != 0 && self.reviewableType) {
if (self.airport == nil && [self.reviewableType isEqualToString:@"Airport"]) {
MyAirport myAirport = <... lookup MyAirport by airportId = reviewableId ...>
if (myAirport) {
self.airport = myAirport;
}
} else if (self.restaurant == nil && [self.reviewableType isEqualToString:@"Restaurant"]) {
MyRestaurant myRestaurant = <... lookup MyRestaurant by restaurantId = reviewableId ...>
if (myRestaurant) {
self.restaurant = myRestaurant;
}
}
}
}
- (void)setReviewableType:(NSString*)reviewableType {
[self willChangeValueForKey:@"reviewableType"];
[self setPrimitiveReviewableType:reviewableType];
[self didChangeValueForKey:@"reviewableType"];
if (self.reviewableId && [self.reviewableId intValue] != 0 && reviewableType) {
if (self.airport == nil && [reviewableType isEqualToString:@"Airport"]) {
MyAirport myAirport = <... lookup MyAirport by airportId = self.reviewableId ...>
if (myAirport) {
self.airport = myAirport;
}
} else if (self.restaurant == nil && [reviewableType isEqualToString:@"Restaurant"]) {
MyRestaurant myRestaurant = <... lookup MyRestaurant by restaurantId = self.reviewableId ...>
if (myRestaurant) {
self.restaurant = myRestaurant;
}
}
}
}
2) KVO для reviewableId и reviewableType - в awakeFromInsert и awakeFromFetch я регистрирую объект модели для наблюдения за его собственными свойствами reviewableId и reviewableType через KVO. Это действительно ужасно, когда объект наблюдает сам, но, по крайней мере, у меня есть один метод, который обрабатывает заполнение ассоциации, что выглядит как улучшение по сравнению с # 1. Обратите внимание, что здесь я заканчиваю некоторыми сбоями с помощью метода removeObserver: forKeyPath: в методе dealloc (например, сбой указывает, что я удалил наблюдателя, которого не было ??), так что на данный момент это далеко от проверенного кода.
- (void)awakeFromFetch {
[super awakeFromFetch];
[self addObserver:self forKeyPath:@"reviewableId" options:0 context:nil];
[self addObserver:self forKeyPath:@"reviewableType" options:0 context:nil];
}
- (void)awakeFromInsert {
[super awakeFromInsert];
[self addObserver:self forKeyPath:@"reviewableId" options:0 context:nil];
[self addObserver:self forKeyPath:@"reviewableType" options:0 context:nil];
}
- (void)dealloc {
[self removeObserver:self forKeyPath:@"reviewableId"];
[self removeObserver:self forKeyPath:@"reviewableType"];
[super dealloc];
}
- (void)updatePolymorphicAssociations {
if (self.reviewableId && [self.reviewableId intValue] != 0 && self.reviewableType) {
if (self.airport == nil && [self.reviewableType isEqualToString:@"Airport"]) {
MyAirport myAirport = <... lookup MyAirport by airportId = self.reviewableId ...>
if (myAirport) {
self.airport = myAirport;
}
} else if (self.restaurant == nil && [self.reviewableType isEqualToString:@"Restaurant"]) {
MyRestaurant myRestaurant = <... lookup MyRestaurant by restaurantId = self.reviewableId ...>
if (myRestaurant) {
self.restaurant = myRestaurant;
}
}
}
}
- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context {
[self updatePolymorphicAssociations];
}
3) Переопределите willSave, используя наш метод updatePolymorphicAssociations сверху. Кажется, это работает, но откладывает все изменения в ассоциации до тех пор, пока мы не сохраним объект, что часто не происходит в то время, когда мы вносим начальные изменения в reviewableId и reviewableType. Кажется также, что здесь есть некоторые ограничения, связанные со вставкой нескольких новых объектов MyReview через фоновый поток, предположительно связанные с willSave, которые фактически вызывают изменение объекта, который мы только что сохранили, и, таким образом, снова запускаем willSave. Даже с проверкой на ноль в updatePolymorphicAssociations я все еще могу завершить работу приложения с помощью этого подхода.
- (void)updatePolymorphicAssociations {
if (self.reviewableId && [self.reviewableId intValue] != 0 && self.reviewableType) {
if (self.airport == nil && [self.reviewableType isEqualToString:@"Airport"]) {
MyAirport myAirport = <... lookup MyAirport by airportId = self.reviewableId ...>
if (myAirport) {
self.airport = myAirport;
}
} else if (self.restaurant == nil && [self.reviewableType isEqualToString:@"Restaurant"]) {
MyRestaurant myRestaurant = <... lookup MyRestaurant by restaurantId = self.reviewableId ...>
if (myRestaurant) {
self.restaurant = myRestaurant;
}
}
}
}
- (void)willSave {
[self updatePolymorphicAssociations];
}
4) Переопределить didSave вместо willSave. Это решает проблемы сохранения с большим импортом в фоновом режиме, который мы получаем с # 3, но затем мы получаем несохраненные изменения, что ужасно.
Итак, у меня, похоже, есть несколько вариантов, но ни один из них не подходит для решения проблемы, которая кажется обычной в приложениях для iOS, с использованием Core Data, поддерживаемой облачной БД Rails. Я чрезмерно анализирую это? Или есть лучший способ?
Обновление: обратите внимание, что цель состоит в том, чтобы установить только одно из отношений в MyReview, в зависимости от того, является ли типableableType "Аэропорт" или "Ресторан". Такая установка отношений сама по себе довольно некрасива, но именно поэтому я задаю этот вопрос. :)