-
-
Notifications
You must be signed in to change notification settings - Fork 266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Provide a plan_record entity type for plan record relationships with metadata #781
Conversation
Really like this approach. Was experimenting some time ago with a similar idea for relationships between logs and assets. It allows adding new and more complex relationship types without adding more and more fields to main entities. When adding a new relationship type, the database structure changes are more contained and don't directly affect entity types the relationship record references. So with this approach, contrib modules that are adding relationship types no longer will need to modify base entity types by adding new fields to them. In my opinion, it's worth considering that this approach could be useful for relationships between other entity types. To that end, maybe the relationship record entity could be made more generic so in the future it could be used in other places, not just for plans. |
That's interesting @wotnak! If we took that approach, I wonder what we would call the entity type. 🤔 Maybe just It would need to be an entity type with no base fields, in order to be able to support referencing any entity type. So it would be up to each bundle to add entity reference bundle fields. The Worth considering! It could be a very flexible option. Although maybe it runs the risk of being too flexible? Since it wouldn't have any base fields, it means that it isn't strictly a "relationship" entity type, necessarily. Someone could use it to do something completely different. I don't know if that's good or bad. The only control we have is what we call the entity, which is why I can't help but also think about the "farm-level data" problem that we've also been discussing on dev calls recently: where do we store data that isn't necessarily tied to any particular asset, including "profile" and "summary" information that you might want to share with third party services? Technically a |
Another question to add to the pile: should we make these entities revisionable? I didn't do that in this simple first pass, but it seems like a logical next step to consider. And the level of effort is low, I think, so if we're going to do it we might as well do it from the start. |
When it comes to the relationship entity, there should probably always be at least two entities involved between which the relationship exists. I was thinking the relationship entity could have two dynamic entity reference base fields that would reference farmOS entities that are in the relationship. Then relationship types (relationship entity bundles) could add more fields, but even without them the relationship would be meaningful (there's a relationship $type between $entity1 and $entity2). When it comes to general metadata, there could be probably generic metadata entity type with one not required dynamic entity reference 'owner' base field that would be used to assign the metadata to a specific entity and if it would be empty then we could assume the metadata is about the farm in general. If the metadata entity was added, then we could consider making relationships bundles of metadata entity that would implement an interface that would indicate the metadata describes a relationship. For the relationship metadata entity, the owner base field could then store the entity that introduced the relationship, it could be one of the entities in the relationship or some other entity. For example, birth log could have a relationship in which the log entity would be the owner, first entity would be the mother asset and second entity the child asset. |
Using generic relationship and/or metadata entities could make the data model more flexible, more easily extendable. But it also could have a big impact on the current data model. For example, instead of creating new log types for every activity, there could be a single one with some quick forms that would simplify adding relationships/metadata for specific activities and would tag the log with a category that would help to determine the activity type at first glance. It would also make some things like building views with entities that have relationships/metadata assigned more complex. This may be worth thinking about, but I don't think that this should block this pr. Plan record entities will be useful right away and if in the future generic relationship/metadata entities would be added, the plan record entities could always be migrated to the new data type. |
These are all great ideas/considerations @wotnak! And very true that we can have a deeper discussion about "metadata" entities in the medium/long term while also moving forward with this PR for plans specifically in the short-term. Perhaps farmOS 4.x could include some larger/breaking data model changes using a new entity type as you describe, and this |
It should also be noted that we can consider experimenting with all of this in contrib, including this new |
This is a lot to take in. Just read the comments above so far... A few thoughts that came to mind:
|
Thanks for the review @symbioquine!
I think that's an open question. There may be many "simple" plans that don't need as much additional information. That said, it's still early and maybe over time a lot of them would end up adopting something like this anyway. I think leaving it optional for now gives them the opportunity, and if we find that it's used more often then not then maybe we make it required in a future release.
Yea I'll take the blame for that. It was the "least bad" name I could think of. 😅 Worth noting, I also considered Certainly open to renaming it, but I think I still like it the best, out of all the options I've considered. |
cf9396d
to
a4e8708
Compare
Rebased onto latest |
a4e8708
to
ce5dedc
Compare
@wotnak sounds like Bundle Classes could be useful for what you are describing: https://www.drupal.org/node/3191609 @mstenta after reviewing how the crop plan is using the The good news is, I don't think we need to do anything in this PR to enable bundle classes. It's something each bundle can opt-in to on their own. Thinking more generally about plan records being re-used for metadata.... one complexity I might foresee is how this abstract entity type would be used to render data on pages. Are there requirements specific to plan record metadata that would be incompatible with other uses of rendering this metadata? And more specifically, do we plan to view/render this
I thought the same. Seems like this could be included with the core plan module but don't have a strong opinion though. |
Pushed two commits... The first adds The second is a patch to |
I suppose there isn't a strong argument for keeping it in a separate module. We will need to add a The benefit of a separate module is that's taken care of. But I agree we may as well put it in the same module if we can. |
Nevermind we have done that before in the quick form module. :-) I pushed a commit that merges the |
I think this is ready for review! |
721aadd
to
32c09f6
Compare
32c09f6
to
ca9c819
Compare
I pushed two more commits to this because I think they are going to be immediately useful in the crop planning module. The first adds a simple access control handler for The second commit exposes a simple |
b4717f7
to
9ca5656
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One aspect of this new plan_record entity type's data model that I want to highlight is: the plan_record entity reference the plan entity.
Seeing how this is implemented here and used within the crop plan I'm really liking this approach. Part of me would like to wait on merging into core until we can do more testing, but understand it would be nice to get this in soon, and it's probably OK to do so.
I'm curious if we should provide a simple view display for these entities as well, but doesn't need to block this PR.
Also not mentioned above but this also appears in JSON:API and works correctly now with the access controller (viewing that is... I didn't test POSTing but suspect that would work fine.)
Regarding Bundle Logic. I've actually found a nice use-case for this: providing a label for the Right now this For the Crop Planting bundle that is used in the crop plan we would likely want to incorporate the associated namespace Drupal\farm_crop_plan\Bundle;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\plan\Entity\PlanRecord;
/**
* Bundle logic for Crop Planting.
*/
class CropPlanting extends PlanRecord {
/**
* {@inheritdoc}
*/
public function label() {
// Build label with the referenced plan and plant.
if ($plan = $this->getPlan()) {
if ($plant = $this->get('plant')->first()?->entity) {
return new TranslatableMarkup('Crop planting: %plant - %plan ', ['%plant' => $plant->label(), '%plan' => $plan->label()]);
}
// Use the plan if no plant reference.
return new TranslatableMarkup('Crop Planting - %plan', ['@plan' => $plan->label()]);
}
// Fallback to default.
return parent::label();
} This label is also used in the entity reference integrity message when preventing access to delete records: |
I love these bundle class ideas too @paul121! Let's experiment with them in downstream code, and maybe it will make sense to adopt them more widely in farmOS core in the near future! I could see them being useful for other entity types too perhaps! |
5a30351
to
18fe606
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added two suggestions related to code style/standards, but overall it looks good to be merged.
*/ | ||
public function getBundleLabel() { | ||
/** @var \Drupal\plan\Entity\PlanRecordTypeInterface $type */ | ||
$type = \Drupal::entityTypeManager() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use of \Drupal::
calls should be avoided in classes (with exception for static methods) instead dependency injection should be used, although in the case of entity types this isn't currently really possible https://www.drupal.org/project/drupal/issues/2142515
but EntityBase class which ContentEntityBase extends provides entityTypeManager()
method, so maybe a good idea would be to use it here instead of directly calling \Drupal::
so when DI capabilities for entity types will be added in core and the EntityBase::entityTypeManager() method will be updated to use it we could automatically take advantage of it without changing farmOS code
$type = \Drupal::entityTypeManager() | |
$type = $this->entityTypeManager() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh good point @wotnak. We actually do this in a few other entity types in farmOS core (most of the time we copy and paste code when creating new entity types). So I might open a separate PR to replace all of them together. Thanks for the heads up!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made a commit in a new 3.x-entitytypemanager
branch on top of this branch: mstenta@ffb1e8f
I'll open a PR to make that change once this is merged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
f58d03d
to
2c1c73d
Compare
Tests are passing, we have three approvals, I think this is good to go! I merged the fixup commit. Marking as "ready to merge"! |
… action only overridden on first entity type
2c1c73d
to
98130de
Compare
This is a proposed solution to Issue #3187877: [META] Plan entity relationships with metadata. Read that thread for the full context and problem statement.
This adds a new
plan_record
entity type, as an optional sub-module of theplan
module. Theplan_record
entity type supports sub-types (bundles), and bundle plugin support is provided by thefarm_entity
module, so that modules can declareplan_record
bundles and their bundle field definitions in PHP files similar to asset, log, and plan types.The implementation is very simple right now. There is no UI or access logic included. The expectation is that these entities will be used primarily by custom code in specific planning modules, which will be responsible for creating and maintaining them for their purposes.
We may want to add some core access logic in the future to support API-based use-cases, but that is not a requirement right now so I kept this barebones so we can consider it.
This new
plan_record
entity type is currently being used in the3.x-plan-record
branch of thefarm_crop_plan
module, which is a v3 port of the crop planning module that we had start in farmOS v1: https://github.com/mstenta/farm_crop_planHere is an example of a bundle of the
plan_record
entity type that thefarm_crop_plan
module provides: https://github.com/mstenta/farm_crop_plan/blob/10862066d473c50d3a09ba4c583b853228270009/src/Plugin/PlanRecord/PlanRecordType/CropPlanting.phpThe crop planning module adds a
crop_planting
bundle of theplan_record
entity type, which is used to link aplant
asset to acrop
plan, along with metadata likeseeding_date
,days_to_transplant
,days_to_harvest
, andharvest_window
. Note that most of these represent the "planned" seeding/transplanting/harvest details, but the module will use seeding/transplanting/harvest logs to represent the "actual" details. This allows capturing and highlighting the differences between "planned" vs "actual", which is useful information to have and highlight in a crop planning UI.One aspect of this new
plan_record
entity type's data model that I want to highlight is: theplan_record
entity reference theplan
entity. Not the other way around. This is the opposite relational direction of theasset
andlog
reference fields that are added toplan
entity bundles by default (note that thecrop
plan overrides thebundeFieldDefinitions()
method so it doesn't use those two default fields).I considered approaching this the other way as well. It would be possible to require
plan
entities to referenceplan_record
entities instead, via one or more entity reference fields on theplan
entity itself. This would be more similar to the defaultasset
andlog
reference fields onplan
entities, or similar to the way that alog
entity referencesquantity
entities.The biggest disadvantage of that approach is that it means you need to manage both the
plan_record
entities and the references to them. For example, if aplant
asset is removed from a plan, you would need to delete theplan_record
entity, then delete the reference to it. There's a risk of having "orphaned"plan_record
entities in the database if they aren't carefully managed (we are currently dealing with that already with quantities: #775). This could lead to issues deleting records that are referenced by the orphanedplan_record
, due to entity reference integrity constraints. On the other hand, if theplan_record
has a requiredplan
reference, it isn't possible for it to be orphaned.The other benefit of
plan_record
entities referencingplan
entities is that we can more easily leverage Migrate API to build CSV importers/updaters for plans. I'm excited to explore this in thefarm_crop_plan
module, to make it easier to build and update crop plans in a spreadsheet, and just use farmOS for the visualization and logging. I'm starting to experiment with that here: https://github.com/mstenta/farm_crop_plan/blob/a34a4574a01ddfc80a98d865ecb202fa9b024a9b/migrations/crop_plan_record.ymlNotice that the
destination.plugin
isentity:plan_record
, which means it is importing/updatingplan_record
entities directly, and keying off of the plan ID and plant asset ID to recognize if it's an new/existing record. This would be much more difficult to achieve if theplan
references theplan_record
entities, because it would also have to be concerned with updating the reference field on the plan.Curious to hear what others think of this approach, and if there are any concerns or considerations I might be overlooking!
I will leave this in draft status for now... if we adopt it we'll also want to add data model docs for the
plan_record
entity type, and maybe some more elaborate usage documentation (although that could also wait until we have more real world examples too).