-
Couldn't load subscription status.
- Fork 0
Nesting
Nesting is built-in to NSRails and is very easy to implement.
For this example, let’s assume we have the model Person, which has_a Brain, and Brain, which belongs_to Person:
#person.rb
class Person < ActiveRecord::Base
has_a :brain
has_many :friends
end
#brain.rb
class Brain < ActiveRecord::Base
belongs_to :person
endOur Objective-C class for Person reflects this association using an array for the has_many, and simply the belongs_to class itself:
//Person.h
@interface Person : NSRRemoteObject
@property (nonatomic, strong) Brain *brain;
@property (nonatomic, strong) NSArray *friends;
@endAlso we need classes for the Brain and Friend models, making sure both inherit from NSRRemoteObject.
-
One-to-one (belongs_to, has_one) – no explicit declaration necessary. If your Person model has_one Brain (or a Brain belongs_to Person), and in Objective-C your
brainproperty is defined as typeBrain *, NSRails will automatically create an instance of that class for that property (assuming that theBrainclass also inherits fromNSRRemoteObject). -
One-to-many or many-to-many relations (has_many) – if your class has_many things (thus the property is an array), you have to define what kind of objects to insert into the array. Otherwise, NSRails will not know what to convert the returned JSON into.
//When updated, "friends" will contain an array of Friend* objects NSRMap(*, friends:Friend)
- If the nested class is not explicitly defined, NSRails will enter NSDictionaries into this array (each with their respective attributes sent from Rails). This will also log a warning. If this is the intended purpose (to have an array of NSDictionaries), add a : to suppress the warning, so
NSRMap(*, friends:). However, it’s highly recommended to use classes, just from an OO standpoint.
For both of these kinds of associations, you will need to make sure that the Rails server includes them when converting Person objects to JSON. Use the following option for the to_json method in the controller methods create, show, and index:
render :json => @person.to_json(:include => [:brain])NSRails will automatically send your associated property (assuming it wasn’t defined as only retrievable) nested in your object’s JSON representation, so all you have to do is support nesting on your Rails app:
#person.rb
class Person < ActiveRecord::Base
has_a :brain
has_many :friends
accepts_nested_attributes_for :brain
accepts_nested_attributes_for :friends
endThis will work for one-to-many or many-to-many associations too.
-
Warning: When using
remoteUpdate, because the update method doesn’t return any information, creating new objects through nesting (eg, adding new elements to an array property) will not set the nested objects’modelIDs. You must do aremoteFetchon your object to set them. For example:
myPerson.brain = [[Brain alloc] init]; [myPerson remoteUpdate]; //Brain object is created on the server, but its modelID is still nil locally //This code will actually create a new Brain object, where not intended! //Since we used *update* above, the brain's ID is nil //This can't be sent to Rails, so, not knowing to update the existing Brain, Rails creates a new one //myPerson.brain.size = @"Average"; //[myPerson remoteUpdate]; //Instead, we must call remoteFetch on myPerson first, which will populate the Brain's modelID field //Remember to see above for retrieving nested models [myPerson remoteFetch]; //Now, updating the person will also update the brain //(since we can send the brain's ID nested along with its new attributes) myPerson.brain.size = @"Average"; [myPerson remoteUpdate];
- In this case, instead of
[myPerson remoteFetch];, you can also consider creating thebrainobject instead. See the belongs_to flag to be able to setbrain.personand have it create the association correctly.
- In this case, instead of
By default, the accepts_nested_attributes_for macro in Rails does not support deleting, so you must make sure to include it:
#person.rb
class Person < ActiveRecord::Base
has_a :brain
has_many :friends
accepts_nested_attributes_for :brain, :allow_destroy => true
accepts_nested_attributes_for :friends, :allow_destroy => true
end-
One-to-one – simply setting the association property to
nil(myPerson.brain = nil) will remove the association (the Brain’sperson_idwill be set tonullin Rails), but won’t actually destroy the Brain record. If you want to delete that record, you must set thedestroyOnNestingproperty (inherited fromNSRRemoteObject) toYES, and then update that main class (Person). Lobotomy example (will destroy the Brain object):
myPerson.brain.destroyOnNesting = YES; [myPerson remoteUpdate];
-
One/many-to-many – in this case, the Objective-C property is an array. Setting this array to
nilwill remove all associations (but not delete those records). However, simply removing an object from this array won’t affect it at all. To delete specific has_many records, you must set their respectivedestroyOnNestingproperties (inherited fromNSRRemoteObject) toYES, and then update that main class (Person). Example to clear all thoughts from your head (will destroy the Thought objects):
for (Thought *thought in myPerson.brain) { thought.destroyOnNesting = YES; } [myPerson.brain remoteUpdate];
- Note: In a many-to-many relationship (using
:through => :confluence_modelin Rails),destroyOnNestingwill only destroy theconfluence_modelrecord connecting the two. It will not destroy the original record. This will require some Rails work with dependent associations (see “Deleting from associations”).
- Note: In a many-to-many relationship (using
Nesting can be tricky at first since it’s the only NSRails feature that requires work on the Rails side too.
For these questions, a Person has_one Brain; and a Brain has_many Thoughts.
-
I entered a nested model class (“Brain”) but it won’t update in Rails when I update the original class (“Person”).
- Rails-side: Make sure that the original model implements “accepts_nested_attributes_for” for the nested model.
accepts_nested_attributes_for :brain - ObjC-side: If it’s a has_many relationship and the related object in Obj-C is an array, make sure you declare what kind of object each will be in the NSRailsify method:
NSRailsify (thoughts:Thought)
- Rails-side: Make sure that the original model implements “accepts_nested_attributes_for” for the nested model.
-
I have a nested class (“Brain”) that won’t refresh when I
remoteFetchthe original class (“Person”).- Rails-side: Make sure that the People controller includes these attributes on the json render (in Show, Create, and Index methods):
"render :json => @person.to_json(:include => {:brain})"
If Brain has_many other Thoughts, you can include it with a nifty trick like so:
"render :json => @person.to_json(:include => {:brain => {:include => :thoughts}})" - ObjC-side: Same answer as #1. Note that if the nested class isn’t declared (
NSRailsify(thoughts)instead ofNSRailsify(thoughts:Thought)), NSRails will enter NSDictionaries with the nested model’s attributes received from Rails into the array.
- Rails-side: Make sure that the People controller includes these attributes on the json render (in Show, Create, and Index methods):
-
I want to remove a nested model relation (“Thought”) when I update the original class, but just removing the item from the array (“thoughts” array in Obj-C) and updating it won’t do anything.
- Yes, if Rails doesn’t see these individual Thoughts inside the
thoughtsarray, it won’t remove them – that array is purely additive (whatever comes in the “thoughts_attributes” array will only be added (unless the ID’s are already defined, which they are through NSRails)), so you must send these Thoughts in and mark them for delete. - Rails-side: set the
:allow_destroyoption onaccepts_nested_attributes_forto be true (false by default). Example for the Brain model:
accepts_nested_attributes_for :thoughts, :allow_destroy => true - ObjC-side: Set each nested object’s
destroyOnNestingproperty (inherited from NSRRemoteObject) to be true. Example to clear all thoughts from a brain:
for (Thought *thought in brain.thoughts) { thought.destroyOnNesting = YES; } [brain updateRemote];
- Yes, if Rails doesn’t see these individual Thoughts inside the