Skip to content

Commit c573f85

Browse files
authored
Merge pull request #35 from /issues/34-Persist_user_code_when_remixing_project
Persist user code when remixing project
2 parents d851378 + a76b4e1 commit c573f85

File tree

13 files changed

+182
-67
lines changed

13 files changed

+182
-67
lines changed

app/concepts/project/operation/create_remix.rb

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,41 @@ module Operation
55
class CreateRemix
66
require 'operation_response'
77

8-
def self.call(params, user_id)
9-
response = OperationResponse.new
10-
11-
validate_params(response, params, user_id)
12-
return response if response.failure?
8+
class << self
9+
def call(params:, user_id:, original_project:)
10+
response = OperationResponse.new
1311

14-
remix_project(response, params, user_id)
15-
response
16-
end
12+
validate_params(response, params, user_id, original_project)
13+
remix_project(response, params, user_id, original_project)
14+
response
15+
end
1716

18-
class << self
1917
private
2018

21-
def validate_params(response, params, user_id)
22-
valid = params[:project_id].present? && user_id.present?
23-
response[:error] = 'Invalid parameters' unless valid
19+
def validate_params(response, params, user_id, original_project)
20+
valid = params[:identifier].present? && user_id.present? && original_project.present?
21+
response[:error] = I18n.t('errors.project.remixing.invalid_params') unless valid
2422
end
2523

26-
def remix_project(response, params, user_id)
27-
original_project = Project.find_by!(identifier: params[:project_id])
24+
def remix_project(response, params, user_id, original_project)
25+
return if response[:error]
2826

29-
response[:project] = create_remix(original_project, user_id)
27+
response[:project] = create_remix(original_project, params, user_id)
3028

31-
response[:error] = 'Unable to create project' unless response[:project].save
29+
response[:error] = I18n.t('errors.project.remixing.cannot_save') unless response[:project].save
3230
response
3331
end
3432

35-
def create_remix(original_project, user_id)
36-
original_project.dup.tap do |proj|
33+
def create_remix(original_project, params, user_id)
34+
remix = original_project.dup.tap do |proj|
3735
proj.user_id = user_id
38-
proj.components = original_project.components.map(&:dup)
3936
proj.remixed_from_id = original_project.id
4037
end
38+
39+
params[:components].each do |x|
40+
remix.components.build(x.slice(:name, :extension, :content, :index))
41+
end
42+
remix
4143
end
4244
end
4345
end

app/concepts/project/operation/update.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module Operation
55
class Update
66
require 'operation_response'
77

8-
def self.call(params, project)
8+
def self.call(params:, project:)
99
response = setup_response(project)
1010

1111
setup_deletions(response, params)

app/controllers/api/projects/remixes_controller.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ class RemixesController < ApiController
66
before_action :require_oauth_user
77

88
def create
9-
result = Project::Operation::CreateRemix.call(remix_params, oauth_user_id)
9+
result = Project::Operation::CreateRemix.call(params: remix_params,
10+
user_id: oauth_user_id,
11+
original_project: project)
1012

1113
if result.success?
1214
@project = result[:project]
@@ -18,8 +20,15 @@ def create
1820

1921
private
2022

23+
def project
24+
@project ||= Project.find_by!(identifier: params[:project_id])
25+
end
26+
2127
def remix_params
22-
params.permit(:project_id)
28+
params.require(:project)
29+
.permit(:name,
30+
:identifier,
31+
components: %i[id name extension content index])
2332
end
2433
end
2534
end

app/controllers/api/projects_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def show
1212
end
1313

1414
def update
15-
result = Project::Operation::Update.call(project_params, @project)
15+
result = Project::Operation::Update.call(params: project_params, project: @project)
1616

1717
if result.success?
1818
render :show, formats: [:json]

config/locales/en.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@ en:
55
delete_default_component: 'Cannot delete default file'
66
change_default_name: 'Cannot amend default file name'
77
change_default_extension: 'Cannot amend default file extension'
8+
remixing:
9+
invalid_params: 'Invalid parameters'
10+
cannot_save: 'Cannot create project remix'

spec/concepts/project/operation/create_remix_spec.rb

Lines changed: 106 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,78 +3,148 @@
33
require 'rails_helper'
44

55
RSpec.describe Project::Operation::CreateRemix, type: :unit do
6-
subject(:create_remix) { described_class.call(params, user_id) }
6+
subject(:create_remix) { described_class.call(params: remix_params, user_id: user_id, original_project: original_project) }
77

88
let(:user_id) { 'e0675b6c-dc48-4cd6-8c04-0f7ac05af51a' }
99
let!(:original_project) { create(:project, :with_components) }
10+
let(:remix_params) do
11+
component = original_project.components.first
12+
{
13+
name: original_project.name,
14+
identifier: original_project.identifier,
15+
components: [
16+
{
17+
id: component.id,
18+
name: component.name,
19+
extension: component.extension,
20+
content: 'some updated component content',
21+
index: component.index
22+
}
23+
]
24+
}
25+
end
1026

1127
before do
1228
mock_phrase_generation
1329
end
1430

1531
describe '.call' do
16-
context 'when all params valid' do
17-
let(:params) { { project_id: original_project.identifier } }
32+
let(:params) { { project_id: original_project.identifier } }
1833

19-
it 'returns success' do
20-
result = create_remix
21-
expect(result.success?).to eq(true)
22-
end
34+
it 'returns success' do
35+
result = create_remix
36+
expect(result.success?).to eq(true)
37+
end
38+
39+
it 'creates new project' do
40+
expect { create_remix }.to change(Project, :count).by(1)
41+
end
42+
43+
it 'assigns a new identifer to new project' do
44+
result = create_remix
45+
remixed_project = result[:project]
46+
expect(remixed_project.identifier).not_to eq(original_project.identifier)
47+
end
48+
49+
it 'assigns user_id to new project' do
50+
remixed_project = create_remix[:project]
51+
expect(remixed_project.user_id).to eq(user_id)
52+
end
53+
54+
it 'duplicates properties on new project' do
55+
remixed_project = create_remix[:project]
2356

24-
it 'creates new project' do
25-
expect { create_remix }.to change(Project, :count).by(1)
57+
remixed_attrs = remixed_project.attributes.symbolize_keys.slice(:name, :project_type)
58+
original_attrs = original_project.attributes.symbolize_keys.slice(:name, :project_type)
59+
expect(remixed_attrs).to eq(original_attrs)
60+
end
61+
62+
it 'creates new components' do
63+
expect { create_remix }.to change(Component, :count).by(1)
64+
end
65+
66+
it 'persists changes made to submitted components' do
67+
remixed_project = create_remix[:project]
68+
expect(remixed_project.components.first.content).to eq('some updated component content')
69+
end
70+
71+
context 'when a new component has been added before remixing' do
72+
let(:new_component_params) { { name: 'added_component', extension: 'py', content: 'some added component content', index: 9999 } }
73+
74+
before do
75+
remix_params[:components] << new_component_params
2676
end
2777

28-
it 'assigns a new identifer to new project' do
29-
result = create_remix
30-
remixed_project = result[:project]
31-
expect(remixed_project.identifier).not_to eq(original_project.identifier)
78+
it 'creates all components' do
79+
expect { create_remix }.to change(Component, :count).by(2)
3280
end
3381

34-
it 'assigns user_id to new project' do
82+
it 'persists the new component' do
3583
remixed_project = create_remix[:project]
36-
expect(remixed_project.user_id).to eq(user_id)
84+
expect(remixed_project.components.last.attributes.symbolize_keys).to include(new_component_params)
3785
end
86+
end
3887

39-
it 'duplicates properties on new project' do
40-
remixed_project = create_remix[:project]
88+
context 'when user_id is not present' do
89+
let(:user_id) { nil }
90+
let(:params) { { project_id: original_project.identifier } }
4191

42-
remixed_attrs = remixed_project.attributes.symbolize_keys.slice(:name, :project_type)
43-
original_attrs = original_project.attributes.symbolize_keys.slice(:name, :project_type)
44-
expect(remixed_attrs).to eq(original_attrs)
92+
it 'returns failure' do
93+
result = create_remix
94+
expect(result.failure?).to eq(true)
4595
end
4696

47-
it 'duplicates project components' do
48-
remixed_props_array = component_array_props(create_remix[:project].components)
49-
original_props_array = component_array_props(original_project.components)
97+
it 'returns error message' do
98+
result = create_remix
99+
expect(result[:error]).to eq(I18n.t('errors.project.remixing.invalid_params'))
100+
end
50101

51-
expect(remixed_props_array).to match_array(original_props_array)
102+
it 'does not create new project' do
103+
expect { create_remix }.not_to change(Project, :count)
52104
end
53105
end
54106

55-
context 'when user_id is not present' do
56-
let(:user_id) { nil }
57-
let(:params) { { project_id: original_project.identifier } }
107+
context 'when original project is not present' do
108+
subject(:create_remix) { described_class.call(params: remix_params, user_id: user_id, original_project: nil) }
58109

59110
it 'returns failure' do
60111
result = create_remix
61112
expect(result.failure?).to eq(true)
62113
end
63114

115+
it 'returns error message' do
116+
result = create_remix
117+
expect(result[:error]).to eq(I18n.t('errors.project.remixing.invalid_params'))
118+
end
119+
64120
it 'does not create new project' do
65121
expect { create_remix }.not_to change(Project, :count)
66122
end
67123
end
68-
end
69124

70-
def component_array_props(components)
71-
components.map do |x|
72-
{
73-
name: x.name,
74-
content: x.content,
75-
extension: x.extension,
76-
index: x.index
77-
}
125+
context 'when project components are invalid' do
126+
let(:invalid_component_params) { { name: 'added_component', extension: 'py', content: '' } }
127+
128+
before do
129+
remix_params[:components] << invalid_component_params
130+
end
131+
132+
it 'returns failure' do
133+
expect(create_remix.failure?).to eq(true)
134+
end
135+
136+
it 'sets error message' do
137+
expect(create_remix[:error]).to eq(I18n.t('errors.project.remixing.cannot_save'))
138+
end
78139
end
79140
end
141+
142+
def component_props(component)
143+
{
144+
name: component.name,
145+
content: component.content,
146+
extension: component.extension,
147+
index: component.index
148+
}
149+
end
80150
end

spec/concepts/project/operation/update_default_component_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
require 'rails_helper'
44

55
RSpec.describe Project::Operation::Update, type: :unit do
6-
subject(:update) { described_class.call(project_params, project) }
6+
subject(:update) { described_class.call(params: project_params, project: project) }
77

88
let!(:project) { create(:project, :with_default_component) }
99
let(:default_component) { project.components.first }

spec/concepts/project/operation/update_delete_components_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
require 'rails_helper'
44

55
RSpec.describe Project::Operation::Update, type: :unit do
6-
subject(:update) { described_class.call(project_params, project) }
6+
subject(:update) { described_class.call(params: project_params, project: project) }
77

88
let!(:project) { create(:project, :with_default_component, :with_components) }
99
let(:component_to_delete) { project.components.last }

spec/concepts/project/operation/update_invalid_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
name: 'updated project name',
99
components: [default_component_params, edited_component_params, new_component_params]
1010
}
11-
described_class.call(params, project)
11+
described_class.call(params: params, project: project)
1212
end
1313

1414
let!(:project) { create(:project, :with_default_component, :with_components, component_count: 2) }

spec/concepts/project/operation/update_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
name: 'updated project name',
99
components: component_params
1010
}
11-
described_class.call(params, project)
11+
described_class.call(params: params, project: project)
1212
end
1313

1414
let!(:project) { create(:project, :with_default_component, :with_components) }

spec/factories/component.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
extension { 'py' }
77
sequence(:index) { |n| n }
88
default { false }
9+
content { Faker::Lorem.paragraph }
910
project
1011

1112
factory :default_python_component do

0 commit comments

Comments
 (0)