Slice-Repo cannot not use top-level struct class

Hey :wave: ,

Just one question during the BETA test:

Intuitively, I thought that a slice repo, like this one

# slices/auth/repos/user_repo
module Auth
  module Repos
    class UserRepo < Auth::DB::Repo
      def all
        users.to_a
      end
    end
  end
end

would use the top-level struct class, if the slice has no user-struct defined. But that’s not the case. The top-level struct is only used if the struct_namespace is explicitly overwritten. Is this desired behavior?

module Auth
  module Repos
    class UserRepo < Auth::DB::Repo
      struct_namespace InhouseGate::Structs
      def all
        users.to_a
      end
    end
  end
end

This question is ambiguous, can you specify what you mean by top-level struct class, and what you are getting instead?

I would expect ROM::Struct to be used if there is no predefined superclass.

By top-level I mean app-level structs. In my opinion, it makes sense to define structs at app level and then share them at the slice repos, if they are not defined at slice-level. In the above case, I created the user-struct at app level and thought it would be used by the slice user-repo out of the box.

It’s not a big issue. I’m just curious if it’s intentional.

Yes, I would say that it is intentional. I will quote from the docs that I’m working on:

A Struct is not a permanent abstraction of a piece of data: it is a momentary projection of the data you requested. This means that instead of a User model that fills every role you need of a user, you could project user data as a Credential for authentication, a Role for authorization, a Visitor for displaying their identity on the page. Every projection can serve a specific purpose, and contain exactly the information you need and nothing more.

What this is intended to get across is that having a singular User model that collects every bit of logic pertaining to users is an anti-pattern. Redefining your User struct is actually desirable, because it makes you think about what you really need for that context.

However, if there is standard presentation logic that you want to share across all Slices, I think this is the most straightforward solution:

# app/structs/user.rb
module InhouseGate
  module Structs
    class User < Hanami::DB::Struct
      def name = "#{given_name} #{family_name}"
    end
  end
end

# slices/auth/structs/user.rb
module Auth
  module Structs
    class User < InhouseGate::User
    end
  end
end

Slices don’t directly manage constants. The dependency graph between slices is based on container keys, and struct classes are an exception to this because they are not containerized.

2 Likes

Thank you for these insights. This helps to get into the right mindset.

Another option is: just define a module! ROM structs are intended to be as close to a PORO as is feasible.

2 Likes

I’m still learning ROM and Hanami, but shouldn’t that be User < InhouseGate::Structs::User?

@postmodern Yes, that would be right. No doubt just a typo/oversight in @alassek written-up code example.

This is correct, it should be InhouseGate::Structs::User.