Core Data - nullify delete rule - objective-c

I have a many-to-many relationship between the entities Groups and Contacts. One group can have many contacts, and one contact can exist in many groups.
Now If I were to delete a group how should I handle all the references to the contacts that it holds?
As I see it there are two options: to use the nullify rule, that would set the deleted group reference to nil for those contacts that had that group. But this sort of introduces a micro-management problem. I then have to check each time I'm listing a contacts groups if any of the groups are nil.
So for me the more elegant solution would be that when a group is deleted, I would delete the group reference completely(?) from the contact object so that it will not contain any nil values for groups at all. Is that possible?

But this sort of introduces a micro-management problem. I then have to
check each time I'm listing a contacts groups if any of the groups are
nil.
No. See this answer to a similar question. If you set the deletion rule for both the Groups->>Contacts and Contacts->>Groups relationships to nullify, then:
deleting a contact will remove that contact from any groups that include it
deleting a group will remove that group from any contacts that belong to it
Collection objects in the Foundation framework (NSArray, NSSet, etc.) never have "nil values." It is possible to store the NSNull singleton as a value in a collection, but that's not what the nullify deletion rule does.
It sounds like the nullify rule is appropriate for both relationships in your situation. The only thing you need to be concerned about is what should happen when you delete all the contacts that belong to a given group or vice versa. A contact that doesn't belong to any groups makes sense, but what should happen to a group that contains no contacts? If a group with no members doesn't make any sense for your app, you'll have to deal with that case.

Related

Correct Delete Rule for Core Data Many-to-Many Relationship?

In my Core Data model I have a Profile entity with a couple of Many-to-Many relationships. One of them is 'Looking For'. Many profiles can be looking for many different things.
The relationships in Core Data have their Delete Rule set to Nullify. What I want to happen is, when a Profile is deleted, all the Looking For entries remain unaffected, and when a Looking For entry is deleted, it should be removed from everyone's Profiles.
This seems like I should be doing No Action for both directions of the relationship between Profile and Looking For, but from googling around and reading docs, I'm not sure this is correct, and in fact No Action seems to be something you would almost never want to use unless you were heavily optimizing. Here's the documentation:
No Action
Do nothing to the object at the destination of the
relationship. For example, if you delete a department, leave all the
employees as they are, even if they still believe they belong to that
department.
It is less obvious why the No Action rule might be of use, since if
you use it you have the possibility of leaving the object graph in an
inconsistent state (employees having a relationship to a deleted
department).
If you use the No Action rule, it is up to you to ensure that the consistency of the object graph
is maintained. You are responsible for setting any inverse relationship to a meaningful value.
This may be of benefit in a situation where you have a to-many relationship and there may be a
large number of objects at the destination.
So I feel like I should be using Nullify instead (which I am currently using), but the documentation for Nullify says:
Nullify
Set the inverse relationship for objects at the destination to
null. For example, if you delete a department, set the department for
all the current members to null. This only makes sense if the
department relationship for an employee is optional, or if you ensure
that you set a new department for each of the employees before the
next save operation.
Now, as written, this seems to imply that if you delete a Profile, every Looking For that was associated with that Profile will have its relationship with Profile emptied, i.e. be removed from all Profiles.
What is the correct thing to do in this case?
"Nullify" is correct. Assume that the Profile p1 is related to the Looking Fors l1, l2, l3. If p1 is deleted and the relationship is set to Nullify, then only
p1 will be removed from the inverse relationships in l1, l2, l3.
With "No Action", the inverse relationships in l1, l2, l3 would remain unchanged,
and therefore point to a non-existing element p1. You would have to remove p1
"manually" from those objects.
In other words, "Nullify" is the simplest rule that keeps the object graph consistent: If a is related to b, and b is deleted, then b is not related to a anymore.

Relationship redundant?

I'm designing a database and I have a user table with users, and a group table with user's group.
These groups will have a owner (a user that has created it), and a set of users that are part of the group (like a Whatsapp group).
To represent this I have this design:
Do you think the owner column on Group table is necessary? Maybe I can add an owner column on Group table I can know easily the group's owner.
If you don't add the owner in the group then where are you going to add it? The only way I see apart from this is adding a boolean isowner to the usergroup. Anyway, this would not make sense if there will only be 1 owner. If there can be N owners then that would be the way to go.
You are on the right track, but you'll need one more step to ensure an owner must actually belong to the group she owns:
There is a FOREIGN KEY in Group {groupID, owner} that references UserGroup {groupID, userID}.
If your DBMS supports deferred foreign keys, you can make owner NOT NULL, to ensure a group cannot be owner-less. Otherwise you can leave it NULL-able (and you will still be able to break the "chicken-and-egg" problem with circular references if your DBMS supports MATCH SIMPLE FKs - almost all do).
You need 4 tables:
User
UserGroup
Group
UserRole (associated with UserGroup) - Shows the role of a user in a group (admin/owner, etc.) - If your roles are Admin and Ordinary user, you could use a Binary column on UserGroup instead.
I know a solution has already been proposed, but I am so convinced there is a better one ...
At the higher level, the concept of owner can be seen as a property of the relation existing between users and groups. It should then ideally be set as a field in the UserGroup table.
It could be either a boolean field or, even better, a generalized userGroupNatureOfRelation field, that could hold values such as 'owner', 'participant', 'user', or whatever could be the status.
Of course, such a solution allows you to implement any specific business rule, like 'there is only one owner per group'. It will let you implement any other more sophisticated business rule when needed, and it will even allow you to add a level of complexity by adding fields such as:
userGroupRelationStartDate
userGroupRelationEndDate
where you'll be able to follow the nature of the relation between a group and a person through time ...
Of course you could say 'I don't need it'. But implementing such an 'open' model does not cost anything more than what you are thinking of now. Then, if for any reason, in a near or far future, business rules have to be changed or improved, your model stays valid and efficient ...
This said, building views and manipulating data should be a lot easier with this model. So this a good and immediate reason to adopt it!

Rails When using dependent destroy and dependent nullify

I want to know about relation dependent destroy and dependent nullify on rails and relation with SQL.
Thanks
Example:
Table users and table cars
user has many cars
car belongs to users
in table car you have user_id on each row
if you set dependent destroy when defining the relationship in users, then when you delete a user, all cars having that user_id will be deleted also
if you set nullify, cars will remain, but the user_id column will be set to null (it is pointless to have any value there because the user with that id was deleted)
Hope that this helps
You use these options when you want to get rid of orphaned records.
Most common used is destroy because it removes all associated objects one by one.
You can use dependent when we want to get rid of orphaned records since they can lead to various problems. Orphaned records are created when we delete or destroy a model A that was associated with model B, but model B wasn't removed in the process.
You most often want to use destroy - all associated objects will remove one by one.
other common options are:
:delete_all – all associated objects will be deleted in a single query.
:nullify – foreign keys will be set to NULL
You can check more details about this here.

How should I record in the database that an item/product is visible by 'all' groups?

A user can be in groups. And an item/product is assigned groups that can see the item. Users can see that item if they are in one of the assigned groups.
I want neither public (anonymous users in no groups) nor groupless users (logged in users not in any groups) to see the item. But I want the interface to allow assigning the item an 'all/any groups' attribute so that users that are in any group at all can see the item.
Where/How should I store this assignment?
p.s. I expect the technique to also be extended to other entities, for example I'd assign a file to a category, and groups are linked to categories. so when a file is marked as visible by the 'all/any category' then if the user (thru groups and group-categories) is linked to at least one category then the file is visible to them.
Decision:
It seemed the choice was whether to implement as a row in a entity-groups table or as fields in the entity table. The chosen answer used the former.
And either managing the group membership in a table or adding JOIN conditions. The chosen answer used the former, but I'm going to use the latter. I'm putting an indirection between the query and usage so if (when) performance is a problem I should be able to change to a managed table underneath (as suggested) without changing usage.
I've other special groups like 'admin', 'users', etc. which can also fit into the same concept (the basis simply being a list of groups) more easily than special and variable field handling for each entity.
thanks all.
I'd put it in the items table as a boolean/bit column IsVisibleToAllGroups.
It does make queries to get all items for a user a bit less straightforward but the other alternative would be to expand out "all groups" so you add a permission row for each individual group but this can potentially lead to a huge expansion in the number of rows and you still have to keep this up-to-date if an additional group is added later and somehow distinguish between a permission that was granted explicitly to all (current and future) groups and one that just happened to be granted to all groups currently in existence.
Edit You don't mention the RDBMS you are using. One other approach you could take would be to have a hierarchy of groups.
GroupId ParentGroupId Name
----------- ------------- ----------
0 NULL Base Group
1 0 Group 1
2 0 Group 2
You could then assign your "all" permissions to GroupId=0 and use (SQL Server approach below)
WITH GroupsForUser
AS (SELECT G.GroupId,
G.ParentGroupId
FROM UserGroups UG
JOIN Groups G
ON G.GroupId = UG.GroupId
WHERE UserId = #UserId
UNION ALL
SELECT G.GroupId,
G.ParentGroupId
FROM Groups G
JOIN GroupsForUser GU
ON G.GroupId = GU.ParentGroupId)
SELECT IG.ItemId
FROM GroupsForUser GU
JOIN ItemGroups IG
ON IG.GroupId = GU.GroupId
As mentioned by both Martin Smith and Mikael Eriksson, making this a property of the entity is a very tidy and straight forward approach. Purely in terms of data representation, this has a very nice feel to it.
I would, however, also consider the queries that you are likely to make against the data. For example, based on your description, you seem most likely to have queries that start with a single user, find the groups they are a member of, and then find the entities they are associated to. Possibly something lke this...
SELECT DISTINCT -- If both user and entity relate to multiple groups, de-dupe them
entity.*
FROM
user
INNER JOIN
user_link_group
ON user.id = user_link_group.user_id
INNER JOIN
group_link_entity
ON group_link_entity.group_id = user_link_group.group_id
INNER JOIN
entity
ON entity.id = group_link_entity.entity_id
WHERE
user.id = #user_id
If you were to use this format, and the idea of a property in the entity table, you would need something much less elegant, and I think the following UNION approach is possibly the most efficient...
<ORIGINAL QUERY>
UNION -- Not UNION ALL, as the next query may duplicate results from above
SELECT
entity.*
FROM
entity
WHERE
EXISTS (SELECT * FROM user_link_group WHERE user_id = #user_id)
AND isVisibleToAllGroups != 0
-- NOTE: This also implies the need for an additional index on [isVisibleToAllGroups]
Rather than create the corner case in the "what entity can I see" query, it is instead an option to create the corner case in the maintenance of the link tables...
Create a GLOBAL group
If an enitity is visible to all groups, map them to the GLOBAL group
If a user is added to a group, ensure they are also linked to the GLOBAL group
If a user is removed from all groups, ensure they are also removed from the GLOBAL group
In this way, the original simple query works without modification. This means that no UNION is needed, with it's overhead of sorting and de-duplication, and neither is the INDEX on isVisibleToAllGroups needed. Instead, the overhead is moved to maintaining which groups a user is linked to; a one time overhead instead.
This assumes that the question "what entities can I see" is more common than changing groups. It also adds a behaviour that is defined by the DATA and not by the SCHEMA, which necessitates good documentation and understanding. As such, I do see this as a powerful type of optimisation, but I also see it as a trades-and-balances type of compromise that needs accounting for in the database design.
Instead of a boolean, which needs additional logic in every query, I'd add a column 'needs_group' which contains the name (or number) of the group that is required for the item. Whether a NULL field means 'nobody' or 'everybody' is only a (allow/deny) design-decision. Creating one 'public' group and putting everybody in it is also a design decision. YMMV
This concept should get you going:
The user can see the product if:
the corresponding row exists in USER_GROUP_PRODUCT
or PRODUCT.PUBLIC is TRUE (and user is in at least one group, if I understand your question correctly).
There are 2 key points to consider about this model:
Liberal usage of identifying relationships - primary keys of parents are "migrated" within primary keys of children, which enables "merging" of GROUP_ID at the bottom USER_GROUP_PRODUCT. This is what allows the DBMS to enforce the constraint that both user and product have to belong to the same group to be mutually visible. Usage of non-identifying relationships and surrogate keys would prevent the DBMS from being able to enforce that directly (you'd have to write custom triggers).
Usage of PRODUCT.PUBLIC - you'll have to treat this field as "magic" in your client code. The alternative is to simply fill the USER_GROUP_PRODUCT with all the possible combinations, but this approach is fragile in case a new user is added - it would not automatically see the product unless you update the USER_GROUP_PRODUCT as well, but how would you know you need to update it unless you have a field such as PRODUCT.PUBLIC? So if you can't avoid PRODUCT.PUBLIC anyway, why not treat it specially and save some storage space in the database?

Core Data - inverse delete rules ... which one should I apply?

I have a 1-to-many relationship in my example app, taken from the Core Data documentation, where one Manager has multiple employees. I get the part on how to set the Manager-to-Employee relationship delete rule, but what about the Employee-to-Manager relationship? If I want a case where, if ALL the employees have been deleted, I want the Manager to also be deleted, what kind of delete rule should I apply? Cascade doesn't make sense, because then if one employee is deleted, the manager will get deleted even though he/she has other employees still linked. Nullify will delete the relationships correctly, but it won't delete the Manager when the last employee has been deleted.
Am I missing something, or do I have to do something custom in this case?
The delete rules don't have enough specificity to say, "delete self if relationship 'bobs' contains fewer than 'x' objects."
Instead, you should put such business logic in a custom NSManagedObject subclass. You can put a check in the Manager classes removeEmployeeObject: and removedEmployeeObjects: method that tells the Manager instances to delete itself if the employees relationship is empty.
You can also use validation methods for this or the willSave methods.

Categories