-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Nested include_association duplication #69
Comments
|
Didn't find anything better than just to re-fill
Which does not sound as a best practice and produces a lot of needless SQL queries. |
Would love to hear about best practices for this question. It's not uncommon to have application schemas were child records have a |
hi @nicbet! sorry for the late response; I need some time to figure out best solution here; so I'll return to you later ;) |
First, I should mention that the design of DB when tables reference in a circle does not look quite good; it could be a sign that something is incorrect in DB design;
There is no guarantee that the left (row) and right (cell) chain of relations are always equal: a_table.rows.flat_map(&:cells) == a_table.columns.flat_map(&:cells) I guess the restriction of correctness relation places someone on the app level, but not on the DB; Anyway, let's consider how to clone this; Option#1: Clone
|
class CellCloner < Clowne::Cloner | |
adapter :active_record | |
finalize do |source, record, **params| | |
mapper = params[:mapper] | |
# remap cells to cloned rows and columns | |
record.row_id = mapper.clone_of(AR::Row.new(id: source.row_id)).id | |
record.column_id = mapper.clone_of(AR::Column.new(id: source.column_id)).id | |
end | |
end |
Usage:
clowne/spec/clowne/integrations/circular_clone_spec.rb
Lines 68 to 81 in dc9b1c9
ActiveRecord::Base.transaction do | |
cloned_table.save! | |
mapper = operation.mapper | |
# replace with your cells selection logic: | |
# cells = Cell.all or | |
# cells = table.rows.flat_map(&:cells) or | |
# cells = table.columns.flat_map(&:cells) or | |
# cells = Cell.where(row_id: table.rows.pluck(:id)) etc. | |
cells.each do |cell| | |
AR::CellCloner.call(cell, mapper: mapper).to_record.save! | |
end | |
end |
NOTE: The solution allows you to fully control what cells
get copied. As I mentioned above, the DB design allows different sets of cells on left and right "relation branches"
I believe the solution could be improved using AR bulk save
@nicbet how do you think Option#2 works for you?
Thanks for the detailed write-up!
it's sometimes unavoidable, especially when you are working with variable tabular data where the number and/or types of columns/rows is not known a-priori - for example when row and column data are created by a user at runtime - any instance of a data grid / data table. However, I'd argue that the relationship model is not circular: a Algorithmically, the data model in such cases is described by a Directed Acyclic Graph (DAG). I believe that's where the point of contention arises: a Cloner assumes that the data model is described by a Tree and will accidentally duplicate records during DFS-traversal for any data model that is not a tree. From a high level perspective, it lacks the concept of a "memory" of visited records to prevent such duplication. Conceptually an example approach to cloning a data model described by a DAG could be:
Option 2 introduces this missing memory into the operation through the From a DX perspective it would be amazing if Clowne could internally support DAG-type data models out of the box - but that may be a feature for another day. If Option 2 is what's officially documented and prescribed - that works for me 👍
I've actually just implemented and tested Option 2 above in a real-world application with two observations:
Thanks again for taking the time to preparing a solution and such a detailed answer. |
I see your point: Agreed
Thanks for sharing your ideas; this is quite an elegant solution; I'm thinking how this can be added without major interventions in the code base; At the same time, I prefer explicit behaviour; What do you think if we introduce a new DSL? restore_association <name>, not_found_strategy: [:fail, :skip, :clone], (default :skip) where
Not sure if we need it for something except |
Hello.
In my app I have a models scheme similar to the next:
and cloners:
Unfortunately, when I try to clone the Table
I get correct table, rows and columns, but cells are cloned twice and it seems that both are wrong:
one cells set has wrong
row_id
from theoriginal_table
rows
and correctcolumn_id
from thecloned_table
columns
and the second cells set has correct
row_id
from thecloned_table
rows
but wrongcolumn_id
from theoriginal_table
columns
It seems that it doesn't understand how to sync all these record cloners (two dimensional table) and does clone
rows
andcolumns
associations independent from each other.Could you help me to understand how to clone such a scheme properly?
Thank you
The text was updated successfully, but these errors were encountered: