Managing dependencies and cascading deletes at the data level in a multi-package scenario

This message was imported from the Ruby/Rails Modularity Slack server. Find more info in the import thread.

Message originally sent by slack user U71V6G2PDJS

How do you think about dependencies at the data level? For instance ActiveRecord ID’s. Example: A user has some notes, so note has a user_id. They are in different packages. Would you have a dependency from the note package to the user package?

How would you deal with cascading deletes in this case? Make note an observer of user deletions somehow?

Event bus delivering a “user deleted” event ?

Message originally sent by slack user U71V6G2PDJS

That would be one way for note to observe on user deletions, yeah :slightly_smiling_face:

Message excluded from import.

Message excluded from import.

Message originally sent by slack user U71V6G2PDJS

That makes sense. I might be overthinking things :slightly_smiling_face:

Message originally sent by slack user U71V6G2PDJS

So a correlating identifier doesn’t imply a dependency then.

Message excluded from import.

Message excluded from import.

Message excluded from import.

Message excluded from import.

Message originally sent by slack user U70NT4Z5OIU

How would you deal with cascading deletes in this case?

Correct me if i’m wrong, but if you’re using packs-rails, aren’t the ActiveRecord objects all loaded into the same namespace? Any cascade deletes can be setup with :dependent.

Message originally sent by slack user U71V6G2PDJS

Yes, you could. But that would be violating the dependency boundaries thus negating what we’re trying to achieve in the first place.

Message originally sent by slack user U71268WT64J

:wave: Hey all

I’d love to know more about this. What we’ve done at Bloom & Wild is add a “Reference” ApplicationRecord module.

Let’s say that we have user_id spread around the system.

We create a ‘User reference’ object:

class UserReference < ApplicationRecord
  include ReferenceObject::ActiveRecord
end

And change things that refer to it to use it:

class SomeClass < ApplicationRecord
  belongs_to :user, class_name: "UserReference"
end

The ReferenceObject::ActiveRecord is a bit complex, but the core of it is this:

module ReferenceObject
  module ActiveRecord
      # Rails supports "ignored_columns", but doesn't have the inverse - something like "allowed_columns"
      # would be very useful here.
      def ignored_columns
        @ignored_columns ||= begin
          # Generate a list of ignored_columns that include all of the actual table
          # columns, excluding the ones in the classattr `allowed_columns`
          cached_names = connection.schema_cache.columns_hash(table_name).map { |name, _| name.to_s }

          (cached_names - ["id"]).freeze
        end
      end
  end
end

Message originally sent by slack user U71268WT64J

This means that “Reference objects” can only have an id field

Message originally sent by slack user U71268WT64J

You could, of course, just store the ID as a numeric field and YOLO

Message originally sent by slack user U71268WT64J

The reason I’ve not done this is because by using a ReferenceObject I can find all places that use that Reference object nice and easily. As opposed to an integer that happens to be called ‘user_id’, say

Message originally sent by slack user U71268WT64J

Additionally, ActiveRecord will double check that the type matches when I try and assign the reference.

This means that if I have (say) user_id and delivery_id it’ll automatically catch stupid copy-paste bugs like this:

SomeObject.create(
  user_id: user_id,
  delivery_id: user_id,
)

Message originally sent by slack user U71268WT64J

We also have this:

UserReference.from(actual_active_record_user)

That will create a new UserReference object from the active_record object

Super interesting approach, Oskar! I will have to try that out locally