Hey ,
I ask this because in certain situations it may be necessary to set i.e. a relation property for an object without reloading it from the DB.
# relation
module MySlice
module Relations
class Orders < MySlice::DB::Relation
schema :orders, infer: true do
associations do
has_many :add_ons
end
end
end
end
end
# struct
module MySlice
module Structs
class Order < MySlice::DB::Struct
end
end
end
# pseudo-code
transaction do
order = step create_order
order = step create_addons(order)
end
def create_add_ons(order)
# after add_on creation, I do not want to reload
# the order with the included add_ons
# Approach 1
# The following should actually be possible with dry struct, but does not
# cause the order-object was not loaded with combine(:add_ons)
order.new(add_ons: created_add_ons)
# Approach 2
# feels cumbersome
# Furthermore the Order struct has to be changed to make this work
order.add_ons = created_add_ons
order
end
Thanks for your response.
Discarding the order variable is only a problem in pseudo code. It does not work even if it is not discarded. add_ons is a relation that can be loaded with combine. The order class here has no add_ons attribute, so the new order also contains no add_ons. Do you think this could be changed at ROM::Struct?
Okay I believe I understand your question now. You want to take one struct schema, an “order”, and produce an entirely new schema that contains “add_ons” which are a relational association. In ROM’s design, these are considered entirely different structs.
This should absolutely be possible, since this is literally what Repository does. I will have to think about it and look into the code though. I have not actually done this myself.
In the interests of treating these structs as generic DTOs in the system, I believe this is a pretty solid use-case that will likely come up again.
The process of turning a Relation dataset into a struct is very, very complicated. I don’t think it’s fruitful to attempt using that plumbing directly.
But in the end, all ROM structs are just dry-struct objects. So here’s how we might approach it from that direction:
I recommend thinking about changing data separately from reading data. If you want to write something to the database, you want to use a separate data representation. Using plain hashes with changesets should suffice.
This is not about writing the data back to the DB. It is about the possibility of using a struct in such a way that data is added to it that is relevant in the further flow.
Using a hash may be a way out here, but the struct offers many advantages.
# pseudo-code
transaction do
order = step create_order
order = step create_addons(order)
# use enriched order in further flow
end
def create_addons(order)
# create addons and return order with addons
order.new(addons: created_addons)
end
If we were querying an order from the db, it would be simple:
order = orders_repo.orders.by_pk(id).combine(:addons).one
The question, as I understand it, is: what if we are creating these two records separately, but want to combine them into one struct after-the-fact?
It seems less than ideal that we should have to make another round-trip to the database when we already have the data, we just need to combine it outside the context of a dataset.
There is a further need for this because changesets don’t support combines presently.