Datos básicos de iOS: enfoque para establecer relaciones para la asociación polimórfica de Rails

Tengo una aplicación para iOS con un model Core Data que imita el model de datos de back-end de My Rails. En el model de back-end de My Rails, estoy usando asociaciones polimórficas para algunas entidades. El model de My Rails se ve así:

Airport < ActiveRecord::Base has_many :reviews, :as => :reviewable Restaurant < ActiveRecord::Base has_many :reviews, :as => :reviewable Review < ActiveRecord::Base belongs_to :reviewable, :polymorphic => :true 

En mi model de datos principales, tengo tres entidades separadas, MyAirport, MyRestaurant y MyReview, con properties relevantes como se indica a continuación:

 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 

Mi pregunta se refiere a la class MyReview en Datos básicos. ¿Cuál es la mejor manera de establecer la relación adecuada de los Datos básicos (por ejemplo, aeropuerto o restaurante) en function de los valores en RevisableId y revisableType?

He intentado lo siguiente, y todo parece un poco sucio por una razón u otra. Hablando en términos generales, necesito asegurarme de que tengo TAN IDENTIFICABLE como revisable y tipo revisable disponibles antes de establecer la asociación correcta, que parece ser el principal problema relacionado con la limpieza percibida. Al hidratar estos objects model de JSON, realmente no controlo el order en que revisableId y reviewableType se rellenan en un nuevo object MyReview.

1) Setters personalizados en uno o ambos revisableId y revisableType – Termino con lógica en cada método setter que comtesting el valor de la otra propiedad para asegurar que esté poblada antes de que pueda establecer la relación aeropuerto o restaurante en el object model MyReview. En términos generales, esto funciona, pero simplemente no se siente bien, mientras que también tiene un código feo duplicado en cada setter.

 - (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 en reviewableId y reviewableType – En awakeFromInsert y awakeFromFetch, logging el object model para observar sus propias properties revisableId y revisableType a través de KVO. Esto se siente realmente feo al tener el object observarse a sí mismo, pero al less tengo un método único que maneja poblar la asociación, lo que parece ser una pequeña mejora sobre el # 1. Tenga en count que termino con algunos lockings aquí con removeObserver: forKeyPath: en el método dealloc (por ejemplo, crash indica que he eliminado un observador que no existía?), Por lo que este punto está lejos de ser un código probado en este punto.

 - (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) Override willSave, utilizando nuestro método updatePolymorphicAssociations desde arriba. Esto parece funcionar, pero anula todos los cambios en la asociación hasta que vamos a save el object, lo que a menudo no ocurre en el momento en que realizamos el cambio inicial a reviewableId y reviewableType. También parece haber algunas limitaciones aquí relacionadas con la inserción de un grupo de nuevos objects MyReview a través de un subprocess de background, presumiblemente relacionado con willSave realmente causando un cambio en el object que estábamos guardando, y por lo tanto desencadenando willSave nuevamente. Incluso con la comprobación nula en updatePolymorphicAssociations, todavía parece poder bloquear la aplicación con este enfoque.

 - (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) Anular didSave, en lugar de willSave. Esto soluciona los problemas de save con grandes importaciones en segundo plano que obtenemos con el n.º 3, pero luego terminamos con cambios no guardados, lo que es feo.

Por lo tanto, parece que tengo muchas opciones aquí, pero ninguna se siente bien para resolver lo que parece ser un problema común en las aplicaciones de iOS que utilizan Core Data respaldado por un DB de nube de Rails. ¿Estoy parsing demasiado esto? ¿O hay un mejor path?

Actualización: Tenga en count que el objective es establecer solo una de las relaciones en MyReview, en function de si reviewableType es "Aeropuerto" o "Restaurante". Esta configuration de la relación es, en sí misma, bastante fea, pero es por eso que hago esta pregunta. 🙂

Pareces estar cometiendo el error cardinal de pensar en Core Data como un contenedor de objects alnetworkingedor de una database relacional. No lo es. ActiveRecord es un contenedor de objects alnetworkingedor de una database relacional, por lo que tiene que usar técnicas como la asociación polimórfica. No puede insert Core Data en el paradigma de wrapper relacional y esperar get un layout elegante.

Por lo que puedo descifrar de su model de datos, en realidad no necesita los attributes reviewablId y reviewableType para crear realmente el gráfico en sí. En su lugar, los replaceía por inheritance y relaciones de entidad. Comienza con un model de datos que se parece así.

 Review{ //... some review attributes place<<-(requinetworking)->ReviewablePlace.reviews //... logically a review must always have a place that is reviewed } ReviewablePlace(abstract){ reviewableID:Number //... in case you have to fetch on this ID number later reviews<-->>Review.place } Airport:Reviewable{ //... some airport specific attributes } Restaurant:Reviewable{ //... some restaurant specific attributes } 

Esto reemplaza la asociación polimórfica con las relaciones de objects y diferencia entre los dos types de lugares revisables haciendo dos subentidades separadas. Dado que la entidad de Review tiene una relación definida con la entidad Reviewable abstracta, también puede formar una relación con cualquiera de las subentidades revisables. Puede search por reviewableID en la entidad revisable y devolverá el object Airport o Restaurant con esa identificación y puede aparecer en la relación de place de un nuevo object Review .

Para procesar sus objects JSON, haría (en psuedocode):

 if Airport fetch on reviewableID if fetch returns existing Airport managed object break //don't want a duplicate so don't create an object else create new Airport managed object with reviewableID break if Restaurant fetch on reviewableID if fetch returns existing Restaurant managed object break //don't want a duplicate so don't create an object else create new Restaurant managed object with reviewableID break if Review fetch on reviewableID if fetch returns Reviewable object of either subclass create new Review object and assign fetched Reviewable object to the Review.place relationship break else create new Airport or Resturant object using the JSON provided reviewableID and reviewableType info create new Review object and assign the newly created Reviewable subclass object to the Review.place relationship break 

Creo que te dará un gráfico de object integral sin tener que saltar a través de aros. En cualquier caso, gran parte de la comprobación que está contemplando en la publicación principal se puede replace con las subentidades y las relaciones adecuadas.