Skip to content

Associate projects with user IDs in the model #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

Merged
merged 10 commits into from
Feb 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions app/concepts/project/operation/create_remix.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

class Project
module Operation
class CreateRemix
require 'operation_response'

def self.call(params)
response = OperationResponse.new

validate_params(response, params)
return response if response.failure?

remix_project(response, params)
response
end

class << self
private

def validate_params(response, params)
valid = params[:phrase_id].present? && params[:remix][:user_id].present?
response[:error] = 'Invalid parameters' unless valid
end

def remix_project(response, params)
original_project = Project.find_by!(identifier: params[:phrase_id])

response[:project] = original_project.dup.tap do |proj|
proj.user_id = params[:remix][:user_id]
proj.components = original_project.components.map(&:dup)
end

response[:error] = 'Unable to create project' unless response[:project].save
response
end
end
end
end
end
15 changes: 0 additions & 15 deletions app/controllers/api/projects/phrases_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,6 @@ def update
head :ok
end

def remix
old = Project.find_by!(identifier: params[:phrase_id])

@project = old.dup
@project.identifier = PhraseIdentifier.generate

old.components.each do |component|
@project.components << component.dup
end

@project.save

render '/api/projects/show', formats: [:json]
end

private

def project_params
Expand Down
24 changes: 24 additions & 0 deletions app/controllers/api/projects/remixes_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

module Api
module Projects
class RemixesController < ApiController
def create
result = Project::Operation::CreateRemix.call(remix_params)

if result.success?
@project = result[:project]
render '/api/projects/show', formats: [:json]
else
render json: { error: result[:error] }, status: :bad_request
end
end

private

def remix_params
params.permit(:phrase_id, remix: [:user_id])
end
end
end
end
2 changes: 1 addition & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

namespace :projects do
resources :phrases, only: %i[show update] do
post 'remix', to: 'phrases#remix'
resource :remix, only: %i[create]
end
end
end
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ services:
- "3009:3009"
depends_on:
- db
stdin_open: true
tty: true

volumes:
pg-data:
13 changes: 13 additions & 0 deletions lib/operation_response.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

class OperationResponse < Hash
def success?
return false unless self[:error].nil?

true
end

def failure?
!success?
end
end
79 changes: 79 additions & 0 deletions spec/concepts/project/operation/create_remix_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Project::Operation::CreateRemix, type: :unit do
subject(:create_remix) { described_class.call(params) }

let(:user_id) { 'e0675b6c-dc48-4cd6-8c04-0f7ac05af51a' }
let!(:original_project) { create(:project, :with_components) }

before do
mock_phrase_generation
end

describe '.call' do
context 'when all params valid' do
let(:params) { { phrase_id: original_project.identifier, remix: { user_id: user_id } } }

it 'returns success' do
result = create_remix
expect(result.success?).to eq(true)
end

it 'creates new project' do
expect { create_remix }.to change(Project, :count).by(1)
end

it 'assigns a new identifer to new project' do
result = create_remix
remixed_project = result[:project]
expect(remixed_project.identifier).not_to eq(original_project.identifier)
end

it 'assigns user_id to new project' do
remixed_project = create_remix[:project]
expect(remixed_project.user_id).to eq(user_id)
end

it 'duplicates properties on new project' do
remixed_project = create_remix[:project]

remixed_attrs = remixed_project.attributes.symbolize_keys.slice(:name, :project_type)
original_attrs = original_project.attributes.symbolize_keys.slice(:name, :project_type)
expect(remixed_attrs).to eq(original_attrs)
end

it 'duplicates project components' do
remixed_props_array = component_array_props(create_remix[:project].components)
original_props_array = component_array_props(original_project.components)

expect(remixed_props_array).to match_array(original_props_array)
end
end

context 'when user_id is not present' do
let(:params) { { phrase_id: original_project.identifier, remix: { user_id: '' } } }

it 'returns failure' do
result = create_remix
expect(result.failure?).to eq(true)
end

it 'does not create new project' do
expect { create_remix }.not_to change(Project, :count)
end
end
end

def component_array_props(components)
components.map do |x|
{
name: x.name,
content: x.content,
extension: x.extension,
index: x.index
}
end
end
end
9 changes: 9 additions & 0 deletions spec/factories/component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

FactoryBot.define do
factory :component do
name { Faker::Lorem.word }
extension { 'py' }
content { Faker::Lorem.paragraph(sentence_count: 2) }
end
end
10 changes: 8 additions & 2 deletions spec/factories/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@

FactoryBot.define do
factory :project do
user_id { rand(10**10) }
user_id { SecureRandom.uuid }
name { Faker::Book.title }
identifier { "#{Faker::Verb.base}-#{Faker::Verb.base}-#{Faker::Verb.base}" }
project_type { %w[python html].sample }
project_type { 'python' }

trait :with_components do
after(:create) do |object|
object.components = FactoryBot.create_list(:component, 2, project: object)
end
end
end
end
40 changes: 40 additions & 0 deletions spec/lib/operation_response_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

require 'spec_helper'
require_relative '../../lib/operation_response'

RSpec.describe OperationResponse do
describe '#success?' do
context 'when :error not present' do
it 'returns true' do
response = described_class.new
expect(response.success?).to eq(true)
end
end

context 'when :error has been set' do
it 'returns false' do
response = described_class.new
response[:error] = 'An error'
expect(response.success?).to eq(false)
end
end
end

describe '#failure?' do
context 'when :error not present' do
it 'returns false' do
response = described_class.new
expect(response.failure?).to eq(false)
end
end

context 'when :error has been set' do
it 'returns true' do
response = described_class.new
response[:error] = 'An error'
expect(response.failure?).to eq(true)
end
end
end
end
2 changes: 1 addition & 1 deletion spec/models/component_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
require 'rails_helper'

RSpec.describe Component, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
it { is_expected.to belong_to(:project) }
end
7 changes: 0 additions & 7 deletions spec/models/word_spec.rb

This file was deleted.

2 changes: 2 additions & 0 deletions spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
abort('The Rails environment is running in production mode!') if Rails.env.production?
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!

Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

# Requires supporting ruby files with custom matchers and macros, etc, in
Expand Down Expand Up @@ -69,6 +70,7 @@
config.filter_rails_from_backtrace!
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")
config.include PhraseIdentifierMock
end

Shoulda::Matchers.configure do |config|
Expand Down
30 changes: 30 additions & 0 deletions spec/request/projects/remix_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'Remix requests', type: :request do
let!(:original_project) { create(:project) }
let(:user_id) { 'e0675b6c-dc48-4cd6-8c04-0f7ac05af51a' }

describe 'create' do
before do
mock_phrase_generation
end

it 'returns expected response' do
post "/api/projects/phrases/#{original_project.identifier}/remix",
params: { remix: { user_id: user_id } }

expect(response.status).to eq(200)
end

context 'when request is invalid' do
it 'returns error response' do
post "/api/projects/phrases/#{original_project.identifier}/remix",
params: { remix: { user_id: '' } }

expect(response.status).to eq(400)
end
end
end
end
10 changes: 10 additions & 0 deletions spec/support/phrase_identifier_mock.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

module PhraseIdentifierMock
def mock_phrase_generation(phrase = nil)
# This could cause problems if tests require multiple phrases to be generated
phrase ||= "#{Faker::Verb.base}-#{Faker::Verb.base}-#{Faker::Verb.base}"

allow(PhraseIdentifier).to receive(:generate).and_return(phrase)
end
end