-
-
Notifications
You must be signed in to change notification settings - Fork 44
Description
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