Custom commands to use with repository's commands macro

Hi there!
I’ve been going through commands guide, but I can’t find a any info on how to build a generic command that can be registered on a repository similarly to built-in create, update, and delete commands. Is it possible to do?

Another semi-related question - is there a way to provide a base class for all repository classes? Let’s say I’d like to have methods defined on all my repositories without using mixins or monkey-patching ROM::Repository::Root class. Is that possible?

Thank you in advance for answering my questions.

PS. Sorry if they might trivial but I’ve just stared looking into ROM.rb, and still finding my ways around docs, and guides.

Yes it is possible although it’s a bit weird because the name of the method must match the command identifier. So, in case of the built-in commands you have commands :create that translates to a corresponding :create command.

This means that if you register a custom command and name it, let’s say, :upsert, then you should be able to do commands :create, :upsert and both command and upsert methods should become available in your repository.

Yes, you actually should have an abstract repository class that inherits from Root and use that in your repo classes. Here’s how it looks in one of my projects:

module Devtools
  class Repository < ROM::Repository::Root
    include Deps[container: "persistence.rom"]

    struct_namespace Structs

Then I have my repos that inherit from it, ie:

require "devtools/repository"

module Devtools
  class PullRequestRepo < Repository[:pull_requests]
    # stuff

Hope this helps. Lemme know if I should explain it better :slightly_smiling_face:

@solnic thanks for the reply. Base class for repository works fine!

I have a problem with custom command though. Can you provide a working example for ROM.rb with rails?

I’ve tried something like this (registered with commands :build on the repo):

class BuildCommand < ROM::SQL::Commands::Create
  relation :projects
  register_as :build

  def execute(tuple)
    # do whatever you need

but it throws error when I try to use it:

ArgumentError: wrong number of arguments (given 1, expected 2+)
from vendor/bundle/ruby/2.7.0/gems/rom-core-5.2.5/lib/rom/command.rb:475:in `apply_hooks'

PS. It also does not work without relation :projects so it’s not generic.

Does it work if you manually do projects.command(:build).call(some_data)?

@solnic it works fine. However gives me the correct entity class, while relation.mapper in the ROM.env.relations[:projects].command(:build).call(some_data) returns false.

To give you more context, I was following Exploding Rails book and I wanted to have a custom build command that creates an empty entity that I can use with Rails form helpers.

If I define build method on the base repo class like this:

def build(attributes = {})

It all works fine. However I wanted to inject to the repo via custom build commands like this: commands :build, :create, update: :by_pk, delete: :by_pk.

This is because repository relations are configured to use auto-struct mapping. You need to give me more context (code-wise) so that I can tell you what to do. ie how your command is defined.

@solnic thanks again. Here is the repository with working code and TODO comments with what I want to achieve - Placeholder for switching to build commands by lowski · Pull Request #1 · lowski/exploding-rails · GitHub (I’ve opened a PR with TODO comments so it’s easier for you to understand my goals), and also see the full application (it’s basically app built with exploding-rails with some of my own modifications).

Feel free to reply in the PR if that’s what you prefer. Thank you once again for taking the time to look into this for me :bowing_man: .

1 Like

@lowski oh I see now what you’re after - I don’t recommend doing this at the command level. Commands are meant to be used for db-specific stuff. Model instantiation should be handled by repositories. I’ll be adding some convenient APIs for that in 6.0.0. Probably something like RootRepository#build would make sense.

Got it, thanks! In this case I will stick to the method on the ApplicationRepository.

1 Like