Skip to content

Commit

Permalink
Merge pull request #18 from /issues/16-Associate_projects_with_user_I…
Browse files Browse the repository at this point in the history
…Ds_in_the_model

Associate projects with user IDs in the model
  • Loading branch information
ArayB authored Feb 25, 2022
2 parents 8e6618e + 99f1f45 commit 4b4fd19
Show file tree
Hide file tree
Showing 15 changed files with 259 additions and 26 deletions.
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

0 comments on commit 4b4fd19

Please sign in to comment.