Skip to content
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

[Question] belongs_to IDs with nested models #51

Closed
vitobotta opened this issue Apr 27, 2020 · 5 comments
Closed

[Question] belongs_to IDs with nested models #51

vitobotta opened this issue Apr 27, 2020 · 5 comments
Labels
awaiting response Awaiting response from the author

Comments

@vitobotta
Copy link

Hi! I am planning a new feature for my app that will need cloning a structure with nested models. There are plan has_many/belongs_to and has_many :trhough associations.

My question: does clowne automatically set the correct/new parent IDs for child models?

Say I have an instance of A1, that has_many B1s. These B1s of course have A1's ID as the id for the belongs_to association. If I clone A1 to A2, A2 will have many B2s. Will these B2s have A2's ID as the parent ID, or A1's ID? And what if I have multiple nesting levels? And what about parent IDs in has_many :through associations?

I hope the question is clear. I am still thinking about how to implement this feature so I am trying to put the pieces together.

Thanks in advance!

@ssnickolay
Copy link
Collaborator

Hey @vitobotta !
Let's me quickly clarify your setup:

class A < ActiveRecord::Base
  has_many :b_records, class_name: "B"
end

class B < ActiveRecord::Base
  belongs_to :a_record, class_name: "A"
end

And you want to do something like this:

class ACloner < Clowne::Cloner
  include_associations :b_records
end

a1 = A.sample
a1.b_record_ids = [B.sample.id, B.sample.id]

operation = ACloner.call(a1)
operation.persist
a2 = operation.to_record

puts a2.b_records_ids
# ???

If so, in this case we will have:

a2.b_record_ids == a1.b_record_ids
# false

because a2 clearly will have a relation with cloned b_records (not with a1.b_records). This relation cloning logic works as deep as you have enough imagination)

p/s/ If I didn't understand you correctly or you have a more complex case - just write here and I will help if I can;)

@ssnickolay ssnickolay added the awaiting response Awaiting response from the author label Apr 27, 2020
@vitobotta
Copy link
Author

Hi @ssnickolay !

Thanks a lot for replying. I am trying to use this gem now and I am almost there.

I have three models: ThemeVersion has many Templates, and Template has many TemplateVersions. These are the cloners:

class ThemeVersionCloner < Clowne::Cloner
	adapter :active_record

	include_association :templates, clone_with: TemplateCloner

	finalize do |_source, record, params|
	  record.current = false
	  record.description = params[:description]
	  record.preview_token = SecureRandom.hex(16)
	end
end
class TemplateCloner < Clowne::Cloner
	adapter :active_record

	include_association :template_versions, clone_with: TemplateVersionCloner
end
class TemplateVersionCloner < Clowne::Cloner
	adapter :active_record

	finalize do |source, record, params|
	  record.path = _source.path.gsub(/\/\d+\//, "/#{record.template.theme_version.id}/")
	end
end

I am having two problems:

  1. The line record.path = _source.path.gsub(/\/\d+\//, "/#{record.template.theme_version.id}/") is not working as expected, because record.template points to the template of the source template version, so the theme version id to replace in the path attribute is wrong. I need the theme version id of the cloned parent (this has to do with a CMS where I am loading liquid templates from the database, and everything is versioned; the path thing is to be able to have multiple templates with the same path for different theme versions, so that the resolver can work properly). So the question is, how can I access the cloned parent?

  2. When dealing with nested models and accessing associations like the parent of the parent, there are a ton of SQL queries. Is there any way to optimise this?

  3. Can I somehow pass parameters to the include_association macro? This may help with 1 and 2.

Thanks in advance!

@vitobotta
Copy link
Author

I got it working with after_persist, but it makes a lot of DB queries. If it's possible to optimise somehow it would be great.

BTW the Documentation link in the README is barely noticeable! Maybe it could be made more prominent. I didn't notice it before so I didn't know about after_persist.

@ssnickolay
Copy link
Collaborator

ssnickolay commented Apr 28, 2020

BTW the Documentation link in the README is barely noticeable! Maybe it could be made more prominent. I didn't notice it before so I didn't know about after_persist.

Hm, sounds reasonable. I'll think what we can do. Thanks 👍

When dealing with nested models and accessing associations like the parent of the parent, there are a ton of SQL queries. Is there any way to optimise this?

You can preload all associations using eager loading

Can I somehow pass parameters to the include_association macro? This may help with 1 and 2.

I think this should helpful for you: https://clowne.evilmartians.io/#/parameters

I got it working with after_persist, but it makes a lot of DB queries.

You can try to implement a store to collect all changes and use bulk upsert (new Rails 6 feature):

class TemplateVersionCloner < Clowne::Cloner
  # adapter :active_record we can skip it

  after_persist do |origin, clone, mapper:, template_version_store: |
    cloned_version = mapper.clone_of(origin.bio)
    template_version_store.unshift({
       id: clone.id,
       path: _source.path.gsub(/\/\d+\//, "/#{cloned_version.template.theme_version.id}/")
    }) # add to the store what we need to change
  end
end

template_version_store = []
theme = Theme.includes(templates: :template_versions).find(params[:id]) # preload what we will clone
operation = ThemeVersionCloner.call(theme, {template_version_store: template_version_store})
operation.persist

puts template_version_store
# list of collected changes

TemplateVersion.upsert_all(template_version_store, unique_by: :id)

@vitobotta
Copy link
Author

@ssnickolay It works beautifully, thanks a lot!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
awaiting response Awaiting response from the author
Projects
None yet
Development

No branches or pull requests

2 participants