Skip to content

Commit 4dc6d78

Browse files
Project serve based on lang (#160)
# What's Changed? - Added ability to specify preferred locale(s) in the REST API request/GraphQL query that will determine the version of the project loaded - Created `ProjectLoader` to choose which version of the project to load based on preferred locales - Refactored GraphQL `project` query and `ProjectsController#show` to use the `ProjectLoader` closes #146 --------- Co-authored-by: loiswells97 <[email protected]> Co-authored-by: Lois Wells <[email protected]> Co-authored-by: Lois Wells <[email protected]>
1 parent 7450619 commit 4dc6d78

File tree

10 files changed

+119
-21
lines changed

10 files changed

+119
-21
lines changed

app/controllers/api/projects_controller.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require 'project_loader'
4+
35
module Api
46
class ProjectsController < ApiController
57
before_action :authorize_user, only: %i[create update index destroy]
@@ -49,7 +51,8 @@ def destroy
4951
private
5052

5153
def load_project
52-
@project = Project.find_by!(identifier: params[:id])
54+
project_loader = ProjectLoader.new(params[:id], [params[:locale]])
55+
@project = project_loader.load
5356
end
5457

5558
def load_projects

app/graphql/types/query_type.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,17 @@ class QueryType < Types::BaseObject
1111

1212
field :project, Types::ProjectType, 'Find a project by identifier' do
1313
argument :identifier, String, required: true, description: 'Project identifier'
14+
argument :preferred_locales, [String], required: false,
15+
description: 'List of preferred project locales, defaults to ["en"]'
1416
end
1517

1618
field :projects, Types::ProjectType.connection_type, 'All viewable projects' do
1719
argument :user_id, String, required: false, description: 'Filter by user ID'
1820
end
1921

20-
def project(identifier:)
21-
Project.find_by(identifier:)
22+
def project(identifier:, preferred_locales: ['en'])
23+
project_loader = ProjectLoader.new(identifier, preferred_locales)
24+
project_loader.load
2225
end
2326

2427
def projects(user_id: nil)

db/schema.graphql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,11 @@ type Query {
549549
Project identifier
550550
"""
551551
identifier: String!
552+
553+
"""
554+
List of preferred project locales, defaults to ["en"]
555+
"""
556+
preferredLocales: [String!]
552557
): Project
553558

554559
"""

lib/project_loader.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# frozen_string_literal: true
2+
3+
class ProjectLoader
4+
attr_reader :identifier, :locale
5+
6+
def initialize(identifier, locales)
7+
@identifier = identifier
8+
@locales = [*locales, 'en', nil]
9+
end
10+
11+
def load
12+
projects = Project.where(identifier:, locale: @locales)
13+
projects.min_by { |project| @locales.find_index(project.locale) }
14+
end
15+
end

spec/graphql/queries/project_query_spec.rb

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
require 'rails_helper'
4+
require 'project_loader'
45

56
RSpec.describe 'query { project { ... } }' do
67
subject(:result) { execute_query(query:, variables:) }
@@ -13,13 +14,20 @@
1314
it { expect(query).not_to be_a_valid_graphql_query }
1415
end
1516

16-
context 'with an identifier' do
17-
let(:query) { 'query ($identifier: String!) { project(identifier: $identifier) { id } }' }
17+
context 'with an identifier and locales' do
18+
let(:query) { 'query ($identifier: String!, $preferred_locales: [String!]) { project(identifier: $identifier, preferredLocales: $preferred_locales) { id } }' }
1819
let(:project) { create(:project, user_id: nil) }
19-
let(:variables) { { identifier: project.identifier } }
20+
let(:variables) { { identifier: project.identifier, preferred_locales: [project.locale, 'another_locale'] } }
2021

2122
it { expect(query).to be_a_valid_graphql_query }
2223

24+
it 'instantiates ProjectLoader with correct arguments' do
25+
allow(ProjectLoader).to receive(:new).and_call_original
26+
result
27+
expect(ProjectLoader).to have_received(:new)
28+
.with(project.identifier, [project.locale, 'another_locale'])
29+
end
30+
2331
it 'returns the project global id' do
2432
expect(result.dig('data', 'project', 'id')).to eq project.to_gid_param
2533
end
@@ -41,7 +49,7 @@
4149
end
4250

4351
context 'when introspecting project components' do
44-
let(:query) { 'query ($identifier: String!) { project(identifier: $identifier) { components { __typename } } }' }
52+
let(:query) { 'query ($identifier: String!, $preferred_locales: [String!]) { project(identifier: $identifier, preferredLocales: $preferred_locales) { components { __typename } } }' }
4553

4654
it { expect(query).to be_a_valid_graphql_query }
4755

@@ -51,7 +59,7 @@
5159
end
5260

5361
context 'when introspecting project images' do
54-
let(:query) { 'query ($identifier: String!) { project(identifier: $identifier) { images { __typename } } }' }
62+
let(:query) { 'query ($identifier: String!, $preferred_locales: [String!]) { project(identifier: $identifier, preferredLocales: $preferred_locales) { images { __typename } } }' }
5563

5664
it { expect(query).to be_a_valid_graphql_query }
5765

@@ -61,7 +69,7 @@
6169
end
6270

6371
context 'when introspecting a remixed project parent' do
64-
let(:query) { 'query ($identifier: String!) { project(identifier: $identifier) { remixedFrom { __typename } } }' }
72+
let(:query) { 'query ($identifier: String!, $preferred_locales: [String!]) { project(identifier: $identifier, preferredLocales: $preferred_locales) { remixedFrom { __typename } } }' }
6573
let(:project) { create(:project, user_id: nil, parent: create(:project, user_id: nil)) }
6674

6775
it { expect(query).to be_a_valid_graphql_query }
File renamed without changes.

spec/lib/project_loader_spec.rb

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
require 'project_loader'
5+
6+
RSpec.describe ProjectLoader do
7+
let(:identifier) { PhraseIdentifier.generate }
8+
let(:preferred_locales) { %w[locale1 locale2] }
9+
let(:loaded_project) do
10+
described_class.new(
11+
identifier,
12+
preferred_locales
13+
).load
14+
end
15+
16+
context 'when projects exist in both preferred locales' do
17+
let!(:preferred_project) { create(:project, identifier:, locale: 'locale1', user_id: nil) }
18+
19+
before do
20+
create(:project, identifier:, locale: 'locale2', user_id: nil)
21+
end
22+
23+
it 'returns the project with the locale highest on list' do
24+
expect(loaded_project).to eq(preferred_project)
25+
end
26+
end
27+
28+
context 'when project exists in second locale but not first' do
29+
let!(:preferred_project) { create(:project, identifier:, locale: 'locale2', user_id: nil) }
30+
31+
it 'returns the project with the locale highest on list' do
32+
expect(loaded_project).to eq(preferred_project)
33+
end
34+
end
35+
36+
context 'when English project with identifier exists' do
37+
let!(:english_project) { create(:project, identifier:, locale: 'en') }
38+
39+
it 'defaults to en' do
40+
expect(loaded_project).to eq(english_project)
41+
end
42+
end
43+
44+
context 'when no preferred locale or English versions but user version exists' do
45+
let!(:user_project) { create(:project, identifier:, locale: nil) }
46+
47+
it 'loads user project' do
48+
expect(loaded_project).to eq(user_project)
49+
end
50+
end
51+
52+
context 'when no project with identifier' do
53+
it 'returns nil' do
54+
expect(loaded_project).to be_nil
55+
end
56+
end
57+
end

spec/requests/projects/destroy_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
let(:user_id) { 'e0675b6c-dc48-4cd6-8c04-0f7ac05af51a' }
77

88
context 'when user is logged in' do
9-
let!(:project) { create(:project, user_id:) }
9+
let!(:project) { create(:project, user_id:, locale: nil) }
1010
let(:headers) { { Authorization: 'dummy-token' } }
1111

1212
before do
@@ -28,7 +28,7 @@
2828
end
2929

3030
context 'when attempting to delete another users project' do
31-
let(:non_owned_project) { create(:project) }
31+
let(:non_owned_project) { create(:project, locale: nil) }
3232

3333
it 'returns forbidden' do
3434
delete("/api/projects/#{non_owned_project.identifier}", headers:)

spec/requests/projects/show_spec.rb

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
require 'rails_helper'
44

55
RSpec.describe 'Project show requests' do
6-
let!(:project) { create(:project) }
6+
let!(:project) { create(:project, locale: nil) }
77
let(:project_json) do
88
{
99
identifier: project.identifier,
@@ -43,12 +43,13 @@
4343
end
4444

4545
context 'when loading another user\'s project' do
46-
let!(:another_project) { create(:project) }
46+
let!(:another_project) { create(:project, locale: nil) }
4747
let(:another_project_json) do
4848
{
4949
identifier: another_project.identifier,
5050
project_type: 'python',
5151
name: another_project.name,
52+
locale: another_project.locale,
5253
user_id: another_project.user_id,
5354
components: [],
5455
image_list: []
@@ -70,7 +71,7 @@
7071

7172
context 'when user is not logged in' do
7273
context 'when loading a starter project' do
73-
let!(:starter_project) { create(:project, user_id: nil) }
74+
let!(:starter_project) { create(:project, user_id: nil, locale: 'ja-JP') }
7475
let(:starter_project_json) do
7576
{
7677
identifier: starter_project.identifier,
@@ -84,25 +85,31 @@
8485
end
8586

8687
it 'returns success response' do
87-
get("/api/projects/#{starter_project.identifier}", headers:)
88-
88+
get("/api/projects/#{starter_project.identifier}?locale=#{starter_project.locale}", headers:)
8989
expect(response).to have_http_status(:ok)
9090
end
9191

9292
it 'returns json' do
93-
get("/api/projects/#{starter_project.identifier}", headers:)
93+
get("/api/projects/#{starter_project.identifier}?locale=#{starter_project.locale}", headers:)
9494
expect(response.content_type).to eq('application/json; charset=utf-8')
9595
end
9696

9797
it 'returns the project json' do
98-
get("/api/projects/#{starter_project.identifier}", headers:)
98+
get("/api/projects/#{starter_project.identifier}?locale=#{starter_project.locale}", headers:)
9999
expect(response.body).to eq(starter_project_json)
100100
end
101101

102102
it 'returns 404 response if invalid project' do
103103
get('/api/projects/no-such-project', headers:)
104104
expect(response).to have_http_status(:not_found)
105105
end
106+
107+
it 'creates a new ProjectLoader with the correct parameters' do
108+
allow(ProjectLoader).to receive(:new).and_call_original
109+
get("/api/projects/#{starter_project.identifier}?locale=#{starter_project.locale}", headers:)
110+
expect(ProjectLoader).to have_received(:new)
111+
.with(starter_project.identifier, [starter_project.locale])
112+
end
106113
end
107114

108115
context 'when loading an owned project' do

spec/requests/projects/update_spec.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
RSpec.describe 'Project update requests' do
66
let(:headers) { { Authorization: 'dummy-token' } }
77
let(:user_id) { 'e0675b6c-dc48-4cd6-8c04-0f7ac05af51a' }
8-
let(:project) { create(:project, user_id:) }
8+
let(:project) { create(:project, user_id:, locale: nil) }
99

1010
context 'when authed user is project creator' do
11-
let(:project) { create(:project, :with_default_component) }
11+
let(:project) { create(:project, :with_default_component, locale: nil) }
1212
let!(:component) { create(:component, project:) }
1313
let(:default_component_params) do
1414
project.components.first.attributes.symbolize_keys.slice(
@@ -79,7 +79,7 @@
7979
end
8080

8181
context 'when authed user is not creator' do
82-
let(:project) { create(:project) }
82+
let(:project) { create(:project, locale: nil) }
8383
let(:params) { { project: { components: [] } } }
8484

8585
before do

0 commit comments

Comments
 (0)