Use-case(s):

The order of images in an article is determined by the delta of media entities in a directional article => media relation.
I rank my friends by how much I like then/when I meet them chronologically/other arbitrary function, and change my mind or remember new data that needs to be inserted.

Possible solutions:

Allow editing of relations, and let the user manipulate the delta values as weights.
Pro: Relatively easy to do, familiar pattern. Con: Conceptually wrong, bad pratice?

Manipulate the weights in memory, create a new relation, and optionally destroy the existing relation.
Pro: Still moderately easy to do. Adds the ability to retain historical data about old relations. Con: ?

Add a weight/integer field to relations.
Since we only have one value per relation, this would require one relation per related object. One can no longer link like this:

   > B
  /
A -> C
  \
   > D

But must instead do

Source | Weight | Target

A -- 1 --> B
A -- 2 --> C
A -- 3 --> D

Pro: Weights become optional. Con: Complexity. Will joining in another table for the weights have performance effects?

It could become a bit problematic to display on a node-edit form, since it's no longer entirely obvious which relations belong together. We could group them by relation type? One possible advantage of this is that something like displaying every other relation per relation type becomes possible.
An example where this could make sense is when an Article can link to Images and Photographers, then one could display every other image/photographer relation.

Fabsor commented on IRC:

an important point about the fields solution
if you use fields you have to load the full entity in the view in order to manipulate thigns
all entities in a view
it also becomes incompatible fairly easily when using pluggable storage, since views simply stops working for fields when you have another storage than db

CommentFileSizeAuthor
#20 relation-weight-1304196-20.patch4.29 KBjelle_s

Comments

Letharion’s picture

Issue summary: View changes

Updated issue summary.

naught101’s picture

Personally, I don't see the problem. Your first graph should actually look like this:

              /-> UserB
UserA -> Likes -> UserC
              \-> UserD

which makes it quite clear what a weird model this is. This is implying that the relationship between userA and userB doesn't exist without the userC and D being part of it! Surely if User C is removed, then the a->B relationship still holds?

I would definitely use binary relations, with a weight field on each (a "strength" field, really):

A -- Likes (strength:10/10) --> B
A -- Likes (strength:8/10) 2 --> C
A -- Likes (strength:5/10) 3 --> D

Views should be able to handle the weight on the relation just fine..

Letharion’s picture

I'm not sure I see a problem any more either, but I still wanted to post it :)

Regardless, would a widget which exposes an int field as a weight have place in the relation module or a sub-module, or should we keep that in a separate project?

naught101’s picture

Oh, definitely. Exposing fields on relation creation is something that would be very nice to include, if done well. Note that the relation_add block already allows this, but it may need more testing.

honza pobořil’s picture

In my experimental module Relation edit widget is sorting feature, which uses proprietal field_weight in a relation entity. We should make some "standardized" way how to save delta of relations.

alanom’s picture

In http://drupal.org/node/1262068 * dpi made the good point that in most cases, weights makes more sense by direction and endpoint than by relation entity.

In most use cases, it makes more sense to ask questions along the lines of, "Who is ACME Ltd [node:producer]'s most important Customer [relation:has_b2b_customer], is it Retail Inc [node:distributor] or Supermarket.com [node:distibutor]?" and then also the separate question in the other direction, "Who is Retail Inc's most important Supplier [relation:has_b2b_customer, reverse], is it ACME Ltd or Megacorp?", and it makes less sense to ask, "Which is more important, ACME Ltd has customer Retail Inc, ACME Ltd has customer Supermarket.com, or Megacorp has customer Retail Inc?". Likewise with images and photographers, it probably makes more sense to order each photographer's images, and if somehow an image has two photographers, their order - rather than order all the facts about which photographers have which images.

Of course, that's not reason not to create the ability to do both types of weighting. I'm sure there are use cases where weighting every relation of a certain type makes sense. It's just a relevant thing to consider that changed how I thought about this problem: it started me working on a field widget for creating, editing and sorting relations and which stores relation rids as a field, so you can then directionally sort the relations like you would any field. It's a little way off being ready to share, and it won't be quality drupal standard code for a long time, so please don't wait for me before working on this!

Getting all this to play nicely in Views with relationships etc is a bit of a pain - it'd be great to see basic pluggable API-like support for weighted relation fields and for non-directional relation weights in relation core as something we can all build upon in a standard way.

* is this a duplicate? The discussion here seems to have gone further though.

naught101’s picture

Yes, marked the other issue as a dup. These arguments really need diagrams for me to be able to make sense of them. Would be interested in seeing your use case as a diagram, alanomaly, because I'm having trouble understanding it.

I'm pretty opposed to having weighted endpoints in relation core, as I don't really see the benefit, as it just makes things more complicated than they already are (binary relations all the way, man!).

On the other hand, relation is supposed to be an API module, so any API stuff that would fit in core that would make this easier to do in contrib would be welcome.

honza pobořil’s picture

I recomend to add option Sortable to Relation type/bundle. This option creates weight/delta field in this type/bundle. All widgets should use this field for sorting. Field could be type number with special widget (or not).

When it will be in the Relation module, I am ready to use it in my Relation edit widget module.

martin_q’s picture

#1359244: Ordering for items in 1:n relationship has been closed as a duplicate of this issue. Like #1, I am not convinced by the use case given in this issue, but there certainly are use cases for weighted endpoints of a relation. Playlists of media items is one.

In response to #6, naught101 has a good point that pretty much everything can be done with binary relations. However, if you add a weight to a relation like that, it's ambiguous. I can't quite think of how to do good diagrams for you but here's an attempt to depict the playlist use case:

Playlist 1 (title "Sticking around"):
1. "Don't leave me this way"
2. "Please don't go"
3. "Stand by me"
4. "Never gonna give you up"

Playlist 2 (title: "Moving on"):
1. "Don't stop"
2. "Je suis venu te dire que je m'en vais"
3. "I will survive"
4. "Go your own way"
5. "Movin' out"

Playlist 3 (title: "Songs with apostrophes in the title")
1. "Je suis venu te dire que je m'en vais"
2. "Don't leave me this way"
3. "Please don't go"
4. "Movin' out"
5. "Don't stop"

The order of songs in a playlist is significant. It is an attribute of the song in that playlist, not globally of the song. Now, it certainly is possible to do this relation with binary relations. But when we add in the weight, is it agnostic about the direction of the relation? I don't think so. You might display one of the above playlist nodes for editing and the song nodes can be rearranged, in order to assign new weights for some or all of those relations (if you move song 5 to first position, all 5 relations would need new weights):

Playlist 2 (title: "Moving on"):    -->  AFTER EDIT:  Playlist 2 (title: "Moving on"):
1. "Don't stop"                                       1. "Movin' out"
2. "Je suis venu te dire que je m'en vais"            2. "Don't stop"
3. "I will survive"                                   3. "Je suis venu te dire que je m'en vais"
4. "Go your own way"                                  4. "I will survive"
5. "Movin' out"                                       5. "Go your own way"

But if you display a song node for editing, you might still show all the playlists it appears in, but this time the weights are completely independent of each other. If you change the weight of the song in one of the playlists, its weight in the others should not alter:

Song "Movin' out"
- is item #5 in Playlist 2 ("Moving on")
- is item #4 in Playlist 3 ("Songs with apostrophes in the title")

AFTER EDIT:
Song "Movin' out"
- is item #1 in Playlist 2 ("Moving on")
- is item #4 in Playlist 3 ("Songs with apostrophes in the title")

So, in this example, the weight is an attribute of the song endpoint for a given relation "playlist contains song", and the direction matters. A hypothetical situation where one might have a weight in the other direction as well is if displaying a given song should result in playlist suggestions. The preferred playlist(s) to suggest might be configurable, and so the order that the playlists for a song are shown in becomes significant too, and is independent of the order of the song in the playlist:

Song "Movin' out"
- (a) is item #1 in Playlist 2 ("Moving on")
- (b) is item #4 in Playlist 3 ("Songs with apostrophes in the title")

NOT THE SAME AS:
Song "Movin' out"
- (a) is item #4 in Playlist 3 ("Songs with apostrophes in the title")
- (b) is item #1 in Playlist 2 ("Moving on")

I don't really mind whether I implement this using many 1:1 relations or a smaller number of 1:n relations. Because of what I've just described, using a weight field on the 1:1 relation is insufficient, but the delta value in the field_data_endpoints table might make sense for a 1:n relation (according to the description of 'delta' in the schema), if weights are only needed in one direction.

So the more I think about it, 1:1 route seems more flexible and powerful, but it would require up to two weight fields on each relation. Is the overhead for editing weights (which might require the loading and saving of all the relations attached to one node in order to reassign all weights) acceptable? Or should we store this in a separate table (rather breaking the principle of fields)?

Whatever the solution, the Relation select module needs much expanding to make it possible to select, edit and reorder more than one endpoint, which I would undertake to do myself if I understood Views better. At the moment there is a separate search field for each relation that one adds, and there are draggable icons next to the endpoint once selected, but these don't do anything... which makes me suspect it's not a difficult fix...

naught101’s picture

See also #461826-10: Generalize to entities for a quite interesting use-case.

honza pobořil’s picture

Could you decide how to do weights? I recommend my solution in #7 :-)

Jorrit’s picture

Component: Code » API

Perhaps this issue is of interest to you: #1664502: Sortable relation_select field instances and endpoints

It certainly doesn't cover all use-cases, but it is a first attempt at making relations sortable with Relation Select.

mikran’s picture

Am I missing something if I think r_index already holds the information of the order of endpoints?

honza pobořil’s picture

mikran: I am not sure if we can safely update r_index later.

alanom’s picture

r-index is the direction of the relation, within one relation's endpoints. This is about the weighting between the endpoints of multiple different relations.

The delta of the relation endpoints fields is potentially an option (a bit odd maybe, as the values are between fields on different entities, but workable). However, things go a bit nuts in Views if you try to use data from the Endpoints field - it's duplicate rows galore.

One approach I made work a while ago was based on simply adding fields to relations (e.g. 'weight_from_source', 'weight_from_target'), and creating a multi-value field widget that syncs its own items' deltas with these fields on the appropriate relations.

This approach has two neat advantages:

  • In a View, you just make sure the relation data is loaded (two relationships, endpoint -> relation and relation -> other endpoint), and sort by the appropriate field (e.g. sort by "weight_from_source"). It just works, and uses Views defaults
  • The UI is nicely simple, conventional and intuitive: weights and order are 100% controlled using the default Drupal drag-and-drop, managed from the appropriate endpoint. For example, in the Playllist example use case above, the order of the tracks from Playlist X is defined by a drag-and-drop list on Playlist X's edit form; and if you cared enough to create the field, the order of the playlists that Track Y was a member of could be controlled in a drag-and-drop on Track Y's edit form. It also makes for a good base for a general relation managing widget - mine uses this field storage to allow relations to be configured on entity create forms for entities that don't exist yet, which are then created automatically as the new entity is inserted on form submit, and I've got mine integrated with some datatables-based autocomplete/filter/caching logic that makes creating relations hunting through 100s/1,000s of potential endpoints pretty easy (I think Relation Wizard UI have recently had a similar idea)

... and two modest disadvantages:

  • Keeping the relations and the fields in sync is not trivial. Lots of code to cover lots of awkward cases and awkward Core behaviours. It works, but requires quite a lot of hooks on important entity or field functions like hook_entity_presave() and hook_field_update(). It might be possible to replace some/most of the syncing logic with dummy field logic, which might be more lightweight in the long run.
  • Choosing sort by "weight_from_source" or "weight_from_target" is fine when it's a 2-endpoint relation, but when it's 3+ endpoints it gets complicated. Defining "weight_from_target_1", "weight_from_target_2" fields works but is constrictive - there's usually no meaningful difference between the 1st and 2nd target on a 3-way relation so a view shouldn't need to be hard-baked to just one of these. Ideally, there would be a dynamic Relation sorting function that works backwards: it takes the relation, takes the Views relationship that loaded it, identifies the endpoint entity which that Views relationship came from, finds the corresponding entity_id and entity_type in the relation's endpoints, looks at the r_index of that endpoint, and chooses the weight field appropriately based on that r_index. This would be great for site builders and allow lots of smart, flexible views to be created, but I have no idea how to get Views to trace its data backwards like that. The necessary data will all be in the view somewhere, but I personally have no idea how to get it. In principle though, it's possible to make this work for relations with any number of endpoints.

(if anyone wants a project and doesn't mind machete-ing through some very messy code, I could be up for sharing what I have with someone who was interested in and serious about potentially releasing and maintaining a proper relation weights/UI module)

wimberb’s picture

Just jumping in here. I had been looking for a method of sorting items in a playlist just like what is described in #8.

My solution was to use the DraggableViews module. It worked perfectly. I was able to drag and drop items into any order I wanted just like you can do with taxonomy terms. But this works right there on the live page.

By using a combination of Views contextual filters and a Viewfield I was able to create just one view but have it display different playlists depending on the page the View was on.

The only real difficulty I had was with the Token for the Viewfield. The normal tokens listed displayed both endpoints of the Relation and thus would not work in the Views contextual filter. After a lot of trial and error I came up with this token that did the trick. [node:relation-is-the-playlist-for-node:0] By appending :0 to the end of the relation token it generated just the first endpoint in the relation which is what I needed to feed into the Contextual Filter.

I hope this helps someone else who is trying to sort endpoints in a list.

ottawadeveloper’s picture

alanom, it looks like I'm going to need this functionality for one of our projects (we're building an online magazine that needs to link articles and issues, with sorting on the issue side at least, and DraggableViews has been disappointing me lately). If you still have the code, I'd love to see what I done to see if it can help me write a module to address this - I think it makes most sense to build it as an extension module, so that people only get the ordering if they need it, eh?

ottawadeveloper’s picture

Assigned: Unassigned » ottawadeveloper

hope this is appropriate. opening the sandbox project: http://drupal.org/sandbox/ottawadeveloper/1909868

naught101’s picture

Re: the playlist issue, I think using an n-ary relation is the wrong way to do things. In fact, I think directional n-ary relations are fundamentally a bad idea in every situation. See my last comment on #1259738: n->m relations (mutant squids)

For albums, which are ordered playlists, the relation should be binary and directional: "is in album/compilation", with a "track number" field. The ordering is easy to change: just fix the fields on which ever relations are wrong.

For other playlists, such as "Songs with apostrophes in the title", there is no logical order, so there is no sensible way to create weights. I would just sort by song title, since that's a common convention. For intentionally ordered playlists, you use the same schema as above, with a simple direction binary relation with a "playlist slot" field, and sort on that.

I don't really know how that fits in with the contrib modules mentioned above, but I'm tempted to call this a won't-fix/works-as-designed for relation core.

naught101’s picture

So, a little aside, here is how to easily replicate node_queue storage with relation.

  1. Create a content type "playlist" (or what ever)
  2. Add an "in playlist" directional relation type that goes from playlists to songs
  3. Add a relation_add field to the playlist content type, with unlimited cardinality
  4. Add a weight (integer) field to the relation type

You now have a weighted playlist. This should work fine views, etc, using both a relation (node->node) views relationship and a relation (node->relation) views relationship.

Unfortunately due to the no-data-on-endpoints nature of relation data storage, we can't use field cardinality for weighting.

Ottowadeveloper: ping this issue when that sandbox is populated with some working code, and I'll add a link to the front page of relation.module.

naught101’s picture

Issue summary: View changes

clean up a bit

jelle_s’s picture

Status: Active » Needs review
StatusFileSize
new4.29 KB

This patch adds a weight column to the relation table. This way relations can be sorted by contrib modules that wish to do so.

If the weight for A -> B should be different than the weight of B -> A it is essentially a different relation and you should make your relation type directional.

mikran’s picture

Version: 7.x-1.x-dev » 8.x-1.x-dev

We have delta and r_index. The delta is not used for anything in relation but this is going to change in d8 (probably). The r_index isn't a thing there but instead delta is used to describe what r_index does now in addition to usual things it does in field api.

This issue is going to be a new feature that won't make it to relation 1.0 that is soon to be released. Instead we should do or atleast decide what to do with endpoint weights in d8 first.

Dynamic Entity Reference provides new edit widgets for drupal 8 version of Relation. There we have drag and drop elements to change the delta(r_index) order of endpoints. So this is about just what issue summary proposes as solution A:

Allow editing of relations, and let the user manipulate the delta values as weights.
Pro: Relatively easy to do, familiar pattern. Con: Conceptually wrong, bad pratice?
attiks’s picture

If I understand correctly the delta is for the ordering of all items of 1 relation. The weight field is for ordering multiple relations.

osopolar’s picture

@mikran: What do you think about the patch in #20. This would be a clean solution, especially thinking of the changing signification of delta and r_index between D7 and D8. Could this go in, so other modules could build on it? Or do you prefer use delta for that purpose for D7?

mikran’s picture

@mikran: What do you think about the patch in #20. This would be a clean solution, especially thinking of the changing signification of delta and r_index between D7 and D8. Could this go in, so other modules could build on it? Or do you prefer use delta for that purpose for D7?

I think that using a field is superior solution to order multiple relations. A global column named 'weight' that is then used by contrib to do something that may or may not be compatible with other contrib modules that use the same column to do something else. There will be different scenarios to weigh the relations so the weight can not be global single value column.

Instead it's better to name that weight field by the actual meaning of the order like track number or media weight. If you want to create a customized list of is track of album -relations then instead of ordering these relations by this magical weight -property you can instead create a new relation customized list and then add all the needed relations to that relation as endpoints and then order those endpoints with r_index / delta.

attiks’s picture

#23, #24 We're using the patch from #20 on a production site with 100.000+ relations and it works like a charm. Adding a field to the relation type is possible but it complicates the code and make everything slower.

osopolar’s picture

I understand @mikran concerns but also would like to have a simple solution for simple/general use-cases. If some relations need some advanced weight handling, then they could implement field based weighting.

My assumption is, that in most cases a simple solution, like the one in #20 would be fine.

The field-based-solution would make contributed modules, which should work for relations in general, like Relation Add module, also more complicated, because there need to be a configuration to define which field should be used for which endpoints weight.

Maybe I am to much focused on my use-case, where I use Relation Add module to add relations and would like that relation module uses the weight set by the tablesort: #1993738: Weight is ignored upon saving a node. To implement that, patch from #20 or something similar needs to be committed - or maybe a won't fix decision on this issue to signalize that there won't be a general implementation for weighted relations.

mikran’s picture

My assumption is, that in most cases a simple solution, like the one in #20 would be fine.

The field-based-solution would make contributed modules, which should work for relations in general, like Relation Add module, also more complicated, because there need to be a configuration to define which field should be used for which endpoints weight.

But both related issues talk about the order of the endpoints and r_index works just fine for that. Patch #20 and the field-based-solution are both related to the relation itself.

osopolar’s picture

But both related issues talk about the order of the endpoints and r_index works just fine for that.

Sorry, I am a bit confused now. Are you talking about D8 or D7 version? You mean that the r_index is or should be used for the order of the endpoints? In D7 the r_index defines if the item is source or target, at least it is used like that in views query – for example to joint only the source endpoint to the node (relation base), so this could not used to sort endpoints.

LEFT JOIN {field_data_endpoints} field_data_endpoints ON node.nid = field_data_endpoints.endpoints_entity_id AND (field_data_endpoints.bundle = 'relation_name' AND field_data_endpoints.endpoints_entity_type = 'node' AND field_data_endpoints.endpoints_r_index = '0')

So in Drupal 7 we could safely use delta for the endpoint order (maybe it is already used somewhere, but at least in the relation add module delta is not (yet) saved to the field_data_endpoints table).

Edit: I took a closer look on the patch in #20, and it's not what I assumed it is. IMHO a weight on the relation itself does not make much sense to me, its like requesting a weight column for the node ore user table. Probably, that could be better done with a field or maybe with something like entityqueues. What I was thinking about is a weight column in the endpoints table, to give each endpoint a wight (i.e. -15 for more important and +15 for less important endpoints). This might also be used in the use-cases where patch #20 was designed for, by just looking on one side of the relation.

The weight column in field_data_endpoints could make sense, at least its function is more clear than a column called r_index or using the delta.

mikran’s picture

Status: Needs review » Active