Skip to content

Attributes on struct (but not relation) raises error when defining a Factory #90

@cllns

Description

@cllns

Describe the bug

I have a custom struct for Article that holds the number of favorites attached to it (as favorites_count). This is computed via a join with the favorites relation.

When trying to create a Factory for Article that has a favorites_count specified, I get an error on ROM::Factory::DSL saying the favorites_count is not defined.

To Reproduce

require "rom"
require "rom-factory"

Types = Dry.Types()
module Structs
  class Article < ROM::Struct
    # Including `title` and `body` is optional, we are able to declare struct-only attributes
    # If you comment these two lines out, we get the same result and same error
    attribute :title, Types::String
    attribute :body, Types::String
    attribute :favorites_count, Types::Integer
  end
end

rom = ROM.container(:sql, "sqlite::memory") do |conf|
  conf.default.create_table(:users) do
    primary_key :id
    column :email, String, null: false
  end

  conf.default.create_table(:articles) do
    primary_key :id
    column :title, String, null: false
    column :body, String, null: false
  end

  conf.default.create_table(:favorites) do
    primary_key %i[user_id article_id]

    foreign_key :user_id, :users
    foreign_key :article_id, :articles
  end

  conf.relation(:users) do
    schema(infer: true)
  end

  conf.relation(:articles) do
    schema(infer: true) do
      associations do
        has_many :favorites
      end
    end

    def with_favorites_count
      left_join(favorites, {id: :article_id})
        .group(:id)
        .select_append { integer.count(:user_id).as(:favorites_count) }
    end
  end

  conf.relation(:favorites) do
    schema(infer: true) do
      associations do
        belongs_to :user
        belongs_to :article
      end
    end
  end
end

class ArticleRepo < ROM::Repository[:articles]
  struct_namespace Structs
  auto_struct true

  def with_favorites_count
    articles.with_favorites_count
  end
end

user1_id = rom.relations[:users].insert(email: "user1@example.com")
user2_id = rom.relations[:users].insert(email: "user2@example.com")
article_id = rom.relations[:articles].insert(title: "Title", body: "Body")

rom.relations[:favorites].insert(user_id: user1_id, article_id: article_id)
rom.relations[:favorites].insert(user_id: user2_id, article_id: article_id)

p ArticleRepo.new(rom).with_favorites_count.to_a
#=> [#<Article title="Title" body="Body" favorites_count=2>]

MyFactory = ROM::Factory.configure do |config|
  config.rom = rom
end

MyFactory.define(:article, struct_namespace: Structs) do |f|
  f.title "Title"
  f.favorites_count(123)
end


# Raises following error:
# ~/.asdf/installs/ruby/3.3.1/lib/ruby/gems/3.3.0/gems/rom-factory-0.12.0/lib/rom/factory/dsl.rb:172:in `method_missing': undefined method `favorites_count' for an instance of ROM::Factory::DSL (NoMethodError)
#	from example.rb:85:in `block in <main>'
#	from /Users/sean/.asdf/installs/ruby/3.3.1/lib/ruby/gems/3.3.0/gems/rom-factory-0.12.0/lib/rom/factory/dsl.rb:48:in `initialize'
#	from /Users/sean/.asdf/installs/ruby/3.3.1/lib/ruby/gems/3.3.0/gems/rom-factory-0.12.0/lib/rom/factory/factories.rb:143:in `new'
#	from /Users/sean/.asdf/installs/ruby/3.3.1/lib/ruby/gems/3.3.0/gems/rom-factory-0.12.0/lib/rom/factory/factories.rb:143:in `define'
#	from example.rb:83:in `<main>'

Expected behavior

I would expect that I could define the factory as above. Then be able to use it with MyFactory[:article] and MyFactory.structs[:article] where the Struct::Article would have a favorites_count of 123.

I think I would expect rom-factory to infer the attributes from the relation, but then also look at the associated struct (in the struct_namespace) to see if there are any other attributes there and allow those to be declared in the factory as well.

This may not be the proper way of doing this, so I'm open to feedback on the approach, and workarounds.

My environment

  • Affects my production application: No
  • Ruby version: 3.3
  • OS: MacOS Sonoma 14.4.1

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions