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

Project copying #2861

Merged
merged 9 commits into from
Aug 20, 2018
Merged

Project copying #2861

merged 9 commits into from
Aug 20, 2018

Conversation

zwolf
Copy link
Member

@zwolf zwolf commented Jul 30, 2018

Adds project copying via a POST /projects/:project_id/copy. Requires that a) the project is not live, b) the project's configuration object includes template: true, and c) the user has write privileges on the project.

Utilizes the deep_cloneable gem to keep the logic clean. This gem just provides a new #dup that takes a list of associations to call the same on. List of associations included (as well as excluded) associations is in the ProjectCopier lib.

Review checklist

  • First, the most important one: is this PR small enough that you can actually review it? Feel free to just reject a branch if the changes are hard to review due to the length of the diff.
  • If there are any migrations, will they the previous version of the app work correctly after they've been run (e.g. the don't remove columns still known about by ActiveRecord).
  • If anything changed with regards to the public API, are those changes also documented in the apiary.apib file?
  • Are all the changes covered by tests? Think about any possible edge cases that might be left untested.

def perform(project_id, user_id)
ProjectCopyWorker.copy(project_id, user_id)
end
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/TrailingBlankLines: Final newline missing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These newlines exist locally and git thinks the files are synced. That is, I can't commit or push them.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a linter for your editor? Something like https://github.com/ngmy/vim-rubocop might help to run it locally as well.

copied_project.save!
copied_project
end
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/TrailingBlankLines: Final newline missing.

:avatar,
:background,
{ active_workflows: [ :tutorials, :attached_images, :workflow_contents ] }
]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/MultilineArrayBraceLayout: Closing array brace must be on the same line as the last array element when opening brace is on the same line as the first array element.

:tagged_resources,
:avatar,
:background,
{ active_workflows: [ :tutorials, :attached_images, :workflow_contents ] }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style/SymbolArray: Use %i or %I for an array of symbols.

@@ -0,0 +1,30 @@
class ProjectCopier
EXCLUDE_ATTRIBUTES = [ :classifications_count, :launched_row_order, :beta_row_order ]
INCLUDE_ATTRIBUTES = [ :project_contents,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style/MutableConstant: Freeze mutable objects assigned to constants.

@@ -0,0 +1,30 @@
class ProjectCopier
EXCLUDE_ATTRIBUTES = [ :classifications_count, :launched_row_order, :beta_row_order ]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style/MutableConstant: Freeze mutable objects assigned to constants.
Style/SymbolArray: Use %i or %I for an array of symbols.

Copy link
Contributor

@camallen camallen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work this looks pretty good, i left some minor comments on your code.

My main interest is in the gem dependency code, i.e. How does the deep clone work under the hood?

A quick read of the source shows that it dups a record, excludes attributes, selects specific attributes and then builds up a chain of associations to link before assigning them to the dup'ed new record.
https://github.com/moiristo/deep_cloneable/blob/1d67dd44031b1df7ae77c8a33b4afd39ade7706f/lib/deep_cloneable.rb#L85

https://github.com/moiristo/deep_cloneable/blob/1d67dd44031b1df7ae77c8a33b4afd39ade7706f/lib/deep_cloneable.rb#L68
Also that looks interesting and the copy is always assumed to be valid. Does this validate: false persist to later saves? Could this cause issues?

Some AR associations auto save the new record (has_many, etc to link the FKs), does this mean that for a small period of time the owner of the template project now owns the new copy as well?

Could it leave the copied template project owned by the non-requesting user? Is this an issue or just something to be aware of (i.e. what happens if the copy fails after the association auto save?

@@ -21,7 +21,7 @@ class TranslateScope < Scope
:create_subjects_export,
:create_workflows_export,
:create_workflow_contents_export,
:retire_subjects, with: WriteScope
:retire_subjects, :copy, with: WriteScope
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How will users copy template projects they don't collab / own? Should this be ReadScope i.e. public projects or ones i can see in my lab?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depends. Write makes sense to me to roll out this feature on a small number of hand selected projects (via the manually setting the configuration) and the button in PFE doesn't exist yet. For those same reasons, though, I can see not mattering very much and maybe it's better to put it in Read now and not have to change it later.

class ProjectCopyWorker
include Sidekiq::Worker

sidekiq_options queue: :data_high
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to look at locking on unique args to ensure users don't trigger multiple copies by double clicking form submission?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

def perform(project_id, user_id)
ProjectCopyWorker.copy(project_id, user_id)
end
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a linter for your editor? Something like https://github.com/ngmy/vim-rubocop might help to run it locally as well.

].freeze

def self.copy(project_id, user_id)
project = Project.find(project_id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens if i can't find the project / user by ID?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️ Good call, added a rescue to the worker.

@@ -0,0 +1,30 @@
class ProjectCopier
EXCLUDE_ATTRIBUTES = [ :classifications_count, :launched_row_order, :beta_row_order ].freeze
INCLUDE_ATTRIBUTES = [ :project_contents,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These aren't really attributes but associations. Should the variable name reflect that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

user = User.find(user_id)

copied_project = project.deep_clone include: INCLUDE_ATTRIBUTES, except: EXCLUDE_ATTRIBUTES
copied_project.owner = user
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you look at the block syntax for updating the clone? https://github.com/moiristo/deep_cloneable#optional-block

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not doing anything with the copy that needs any pre-exclude original attributes or anything else I can think of that requires that block. It's all happening pre-save!, so it's not hitting the db yet.


it "has matching attributes" do
expect(copied_project.display_name).to eq(project.display_name)
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to test the other changes to the copied instance as well, e.g. copied_project.update_attributes(launch_approved: false, live: false)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

@zwolf
Copy link
Member Author

zwolf commented Aug 2, 2018

Thanks, updated with suggested changes. Also, somehow we both managed to miss that I was calling the ProjectCopyWorker from itself instead of the ProjectCopier. There are specs for that, now, too. :3

Also that looks interesting and the copy is always assumed to be valid. Does this validate: false persist to later saves? Could this cause issues?

First, that would only happen using the validate: false option, which isn't being used, so all saves should be being validated. But also, there's only the one save before the Worker, the Copier instance, and everything else is garbage collected so I don't see how that option could persist.

Some AR associations auto save the new record (has_many, etc to link the FKs), does this mean that for a small period of time the owner of the template project now owns the new copy as well? Could it leave the copied template project owned by the non-requesting user? Is this an issue or just something to be aware of (i.e. what happens if the copy fails after the association auto save?

I didn't see that happening. Running a deep clone on a project, it's workflows are unsaved until you save the project. As a matter of fact, a copied_project.workflows is empty while copied_project.active_workflows contains the unsaved workflow. When you save! and reload the copied_project, the .workflows association exists and it can be pulled from the db.

}.not_to raise_error
end
end
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/TrailingBlankLines: Final newline missing.

@@ -1,4 +1,4 @@
class SubjectWorkflowStatusCreateWorker
class SubjectWorkflowStatusCreateWorker

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/InitialIndentation: Indentation of first line in file detected.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aw c'mon, The Hound, this wasn't even--aw, forget it. Fine, I'll fix it.

Copy link
Contributor

@camallen camallen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice - my original comments were misfounded and your interpretation is correct. This gem doesn't use the validate option unless specified and doesn't cause auto save on certain associations to fire (still not 100% sure why though).

I was thinking should we store the originating copy ID in the project configuration for data provenance? That way we can see which project it was copied from if we have to debug it.

A couple of minor comments on this PR, nice work 👍

@@ -0,0 +1,29 @@
class ProjectCopier
EXCLUDE_ASSOCIATIONS = [ :classifications_count, :launched_row_order, :beta_row_order ].freeze
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These aren't associations, rather attributes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doh! shouldn't have changed both.

copied_project.display_name += " (copy)"
end

copied_project.update_attributes(launch_approved: false, live: false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will run a save after assigning the attributes, i think you want assign_attributes instead to avoid the double save.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

absolutely, ✔️

describe '#copy' do
let(:project) { create(:full_project, build_extra_contents: true)}
let(:copyist) { create(:user) }
let(:tags) { create(:tag, resource: project) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

none of these associated records exist as they are never called before the copy. Might want to add a let!(:tags) or call them before you use them. Also you should probably test these associations exist on the copied project, i wouldn't validate the copy just that the included associations are copied.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup. bang!

:avatar,
:background,
:translations,
{ active_workflows: [ :tutorials, :attached_images, :workflow_contents] }].freeze

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style/SymbolArray: Use %i or %I for an array of symbols.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants