Commit 645e8d470559b07a22164c55b76195a60fb8b37b
1 parent
0f473674
Exists in
spb-stable
and in
3 other branches
Move services for collecting items to Finders
Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
Showing
16 changed files
with
296 additions
and
238 deletions
Show diff stats
app/controllers/dashboard_controller.rb
... | ... | @@ -53,13 +53,13 @@ class DashboardController < ApplicationController |
53 | 53 | end |
54 | 54 | |
55 | 55 | def merge_requests |
56 | - @merge_requests = FilteringService.new.execute(MergeRequest, current_user, params) | |
56 | + @merge_requests = MergeRequestsFinder.new.execute(current_user, params) | |
57 | 57 | @merge_requests = @merge_requests.page(params[:page]).per(20) |
58 | 58 | @merge_requests = @merge_requests.preload(:author, :target_project) |
59 | 59 | end |
60 | 60 | |
61 | 61 | def issues |
62 | - @issues = FilteringService.new.execute(Issue, current_user, params) | |
62 | + @issues = IssuesFinder.new.execute(current_user, params) | |
63 | 63 | @issues = @issues.page(params[:page]).per(20) |
64 | 64 | @issues = @issues.preload(:author, :project) |
65 | 65 | ... | ... |
app/controllers/groups_controller.rb
... | ... | @@ -47,13 +47,13 @@ class GroupsController < ApplicationController |
47 | 47 | end |
48 | 48 | |
49 | 49 | def merge_requests |
50 | - @merge_requests = FilteringService.new.execute(MergeRequest, current_user, params) | |
50 | + @merge_requests = MergeRequestsFinder.new.execute(current_user, params) | |
51 | 51 | @merge_requests = @merge_requests.page(params[:page]).per(20) |
52 | 52 | @merge_requests = @merge_requests.preload(:author, :target_project) |
53 | 53 | end |
54 | 54 | |
55 | 55 | def issues |
56 | - @issues = FilteringService.new.execute(Issue, current_user, params) | |
56 | + @issues = IssuesFinder.new.execute(current_user, params) | |
57 | 57 | @issues = @issues.page(params[:page]).per(20) |
58 | 58 | @issues = @issues.preload(:author, :project) |
59 | 59 | |
... | ... | @@ -100,7 +100,7 @@ class GroupsController < ApplicationController |
100 | 100 | end |
101 | 101 | |
102 | 102 | def projects |
103 | - @projects ||= Projects::CollectService.new.execute(current_user, group: group) | |
103 | + @projects ||= ProjectsFinder.new.execute(current_user, group: group) | |
104 | 104 | end |
105 | 105 | |
106 | 106 | def project_ids | ... | ... |
app/controllers/projects/issues_controller.rb
... | ... | @@ -121,7 +121,7 @@ class Projects::IssuesController < Projects::ApplicationController |
121 | 121 | def issues_filtered |
122 | 122 | params[:scope] = 'all' if params[:scope].blank? |
123 | 123 | params[:state] = 'opened' if params[:state].blank? |
124 | - @issues = FilteringService.new.execute(Issue, current_user, params.merge(project_id: @project.id)) | |
124 | + @issues = IssuesFinder.new.execute(current_user, params.merge(project_id: @project.id)) | |
125 | 125 | end |
126 | 126 | |
127 | 127 | # Since iids are implemented only in 6.1 | ... | ... |
app/controllers/projects/merge_requests_controller.rb
... | ... | @@ -21,7 +21,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController |
21 | 21 | params[:scope] = 'all' if params[:scope].blank? |
22 | 22 | params[:state] = 'opened' if params[:state].blank? |
23 | 23 | |
24 | - @merge_requests = FilteringService.new.execute(MergeRequest, current_user, params.merge(project_id: @project.id)) | |
24 | + @merge_requests = MergeRequestsFinder.new.execute(current_user, params.merge(project_id: @project.id)) | |
25 | 25 | @merge_requests = @merge_requests.page(params[:page]).per(20) |
26 | 26 | |
27 | 27 | @sort = params[:sort].humanize | ... | ... |
app/controllers/projects/notes_controller.rb
... | ... | @@ -5,7 +5,7 @@ class Projects::NotesController < Projects::ApplicationController |
5 | 5 | before_filter :authorize_admin_note!, only: [:update, :destroy] |
6 | 6 | |
7 | 7 | def index |
8 | - @notes = Notes::LoadService.new(project, current_user, params).execute | |
8 | + @notes = NotesFinder.new.execute(project, current_user, params) | |
9 | 9 | |
10 | 10 | notes_json = { notes: [] } |
11 | 11 | ... | ... |
... | ... | @@ -0,0 +1,24 @@ |
1 | +# Finders | |
2 | + | |
3 | +This type of classes responsible for collectiong items based on different conditions. | |
4 | +To prevent lookup methods in models like this: | |
5 | + | |
6 | +``` | |
7 | +class Project | |
8 | + def issues_for_user_filtered_by(user, filter) | |
9 | + # A lot of logic not related to project model itself | |
10 | + end | |
11 | +end | |
12 | + | |
13 | +issues = project.issues_for_user_filtered_by(user, params) | |
14 | +``` | |
15 | + | |
16 | +Better use this: | |
17 | + | |
18 | +``` | |
19 | +selector = Finders::Issues.new | |
20 | + | |
21 | +issues = selector.execute(project, user, filter) | |
22 | +``` | |
23 | + | |
24 | +It will help keep models thiner | ... | ... |
... | ... | @@ -0,0 +1,137 @@ |
1 | +# BaseFinder | |
2 | +# | |
3 | +# Used to filter Issues and MergeRequests collections by set of params | |
4 | +# | |
5 | +# Arguments: | |
6 | +# klass - actual class like Issue or MergeRequest | |
7 | +# current_user - which user use | |
8 | +# params: | |
9 | +# scope: 'created-by-me' or 'assigned-to-me' or 'all' | |
10 | +# state: 'open' or 'closed' or 'all' | |
11 | +# group_id: integer | |
12 | +# project_id: integer | |
13 | +# milestone_id: integer | |
14 | +# assignee_id: integer | |
15 | +# search: string | |
16 | +# label_name: string | |
17 | +# sort: string | |
18 | +# | |
19 | +class BaseFinder | |
20 | + attr_accessor :current_user, :params | |
21 | + | |
22 | + def execute(current_user, params) | |
23 | + @current_user = current_user | |
24 | + @params = params | |
25 | + | |
26 | + items = init_collection | |
27 | + items = by_scope(items) | |
28 | + items = by_state(items) | |
29 | + items = by_group(items) | |
30 | + items = by_project(items) | |
31 | + items = by_search(items) | |
32 | + items = by_milestone(items) | |
33 | + items = by_assignee(items) | |
34 | + items = by_label(items) | |
35 | + items = sort(items) | |
36 | + end | |
37 | + | |
38 | + private | |
39 | + | |
40 | + def init_collection | |
41 | + table_name = klass.table_name | |
42 | + | |
43 | + if project | |
44 | + if project.public? || (current_user && current_user.can?(:read_project, project)) | |
45 | + project.send(table_name) | |
46 | + else | |
47 | + [] | |
48 | + end | |
49 | + elsif current_user && params[:authorized_only].presence | |
50 | + klass.of_projects(current_user.authorized_projects) | |
51 | + else | |
52 | + klass.of_projects(Project.accessible_to(current_user)) | |
53 | + end | |
54 | + end | |
55 | + | |
56 | + def by_scope(items) | |
57 | + case params[:scope] | |
58 | + when 'created-by-me', 'authored' then | |
59 | + items.where(author_id: current_user.id) | |
60 | + when 'all' then | |
61 | + items | |
62 | + when 'assigned-to-me' then | |
63 | + items.where(assignee_id: current_user.id) | |
64 | + else | |
65 | + raise 'You must specify default scope' | |
66 | + end | |
67 | + end | |
68 | + | |
69 | + def by_state(items) | |
70 | + case params[:state] | |
71 | + when 'closed' | |
72 | + items.closed | |
73 | + when 'all' | |
74 | + items | |
75 | + when 'opened' | |
76 | + items.opened | |
77 | + else | |
78 | + raise 'You must specify default state' | |
79 | + end | |
80 | + end | |
81 | + | |
82 | + def by_group(items) | |
83 | + if params[:group_id].present? | |
84 | + items = items.of_group(Group.find(params[:group_id])) | |
85 | + end | |
86 | + | |
87 | + items | |
88 | + end | |
89 | + | |
90 | + def by_project(items) | |
91 | + if params[:project_id].present? | |
92 | + items = items.of_projects(params[:project_id]) | |
93 | + end | |
94 | + | |
95 | + items | |
96 | + end | |
97 | + | |
98 | + def by_search(items) | |
99 | + if params[:search].present? | |
100 | + items = items.search(params[:search]) | |
101 | + end | |
102 | + | |
103 | + items | |
104 | + end | |
105 | + | |
106 | + def sort(items) | |
107 | + items.sort(params[:sort]) | |
108 | + end | |
109 | + | |
110 | + def by_milestone(items) | |
111 | + if params[:milestone_id].present? | |
112 | + items = items.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id])) | |
113 | + end | |
114 | + | |
115 | + items | |
116 | + end | |
117 | + | |
118 | + def by_assignee(items) | |
119 | + if params[:assignee_id].present? | |
120 | + items = items.where(assignee_id: (params[:assignee_id] == '0' ? nil : params[:assignee_id])) | |
121 | + end | |
122 | + | |
123 | + items | |
124 | + end | |
125 | + | |
126 | + def by_label(items) | |
127 | + if params[:label_name].present? | |
128 | + items = items.tagged_with(params[:label_name]) | |
129 | + end | |
130 | + | |
131 | + items | |
132 | + end | |
133 | + | |
134 | + def project | |
135 | + Project.where(id: params[:project_id]).first if params[:project_id].present? | |
136 | + end | |
137 | +end | ... | ... |
... | ... | @@ -0,0 +1,22 @@ |
1 | +# Finders::Issues class | |
2 | +# | |
3 | +# Used to filter Issues collections by set of params | |
4 | +# | |
5 | +# Arguments: | |
6 | +# current_user - which user use | |
7 | +# params: | |
8 | +# scope: 'created-by-me' or 'assigned-to-me' or 'all' | |
9 | +# state: 'open' or 'closed' or 'all' | |
10 | +# group_id: integer | |
11 | +# project_id: integer | |
12 | +# milestone_id: integer | |
13 | +# assignee_id: integer | |
14 | +# search: string | |
15 | +# label_name: string | |
16 | +# sort: string | |
17 | +# | |
18 | +class IssuesFinder < BaseFinder | |
19 | + def klass | |
20 | + Issue | |
21 | + end | |
22 | +end | ... | ... |
... | ... | @@ -0,0 +1,22 @@ |
1 | +# Finders::MergeRequest class | |
2 | +# | |
3 | +# Used to filter MergeRequests collections by set of params | |
4 | +# | |
5 | +# Arguments: | |
6 | +# current_user - which user use | |
7 | +# params: | |
8 | +# scope: 'created-by-me' or 'assigned-to-me' or 'all' | |
9 | +# state: 'open' or 'closed' or 'all' | |
10 | +# group_id: integer | |
11 | +# project_id: integer | |
12 | +# milestone_id: integer | |
13 | +# assignee_id: integer | |
14 | +# search: string | |
15 | +# label_name: string | |
16 | +# sort: string | |
17 | +# | |
18 | +class MergeRequestsFinder < BaseFinder | |
19 | + def klass | |
20 | + MergeRequest | |
21 | + end | |
22 | +end | ... | ... |
... | ... | @@ -0,0 +1,17 @@ |
1 | +class NotesFinder | |
2 | + def execute(project, current_user, params) | |
3 | + target_type = params[:target_type] | |
4 | + target_id = params[:target_id] | |
5 | + | |
6 | + case target_type | |
7 | + when "commit" | |
8 | + project.notes.for_commit_id(target_id).not_inline.fresh | |
9 | + when "issue" | |
10 | + project.issues.find(target_id).notes.inc_author.fresh | |
11 | + when "merge_request" | |
12 | + project.merge_requests.find(target_id).mr_and_commit_notes.inc_author.fresh | |
13 | + when "snippet" | |
14 | + project.snippets.find(target_id).notes.fresh | |
15 | + end | |
16 | + end | |
17 | +end | ... | ... |
... | ... | @@ -0,0 +1,63 @@ |
1 | +class ProjectsFinder | |
2 | + def execute(current_user, options) | |
3 | + group = options[:group] | |
4 | + | |
5 | + if group | |
6 | + group_projects(current_user, group) | |
7 | + else | |
8 | + all_projects(current_user) | |
9 | + end | |
10 | + end | |
11 | + | |
12 | + private | |
13 | + | |
14 | + def group_projects(current_user, group) | |
15 | + if current_user | |
16 | + if group.users.include?(current_user) | |
17 | + # User is group member | |
18 | + # | |
19 | + # Return ALL group projects | |
20 | + group.projects | |
21 | + else | |
22 | + projects_members = UsersProject.where( | |
23 | + project_id: group.projects, | |
24 | + user_id: current_user | |
25 | + ) | |
26 | + | |
27 | + if projects_members.any? | |
28 | + # User is a project member | |
29 | + # | |
30 | + # Return only: | |
31 | + # public projects | |
32 | + # internal projects | |
33 | + # joined projects | |
34 | + # | |
35 | + group.projects.where( | |
36 | + "projects.id IN (?) OR projects.visibility_level IN (?)", | |
37 | + projects_members.pluck(:project_id), | |
38 | + Project.public_and_internal_levels | |
39 | + ) | |
40 | + else | |
41 | + # User has no access to group or group projects | |
42 | + # | |
43 | + # Return only: | |
44 | + # public projects | |
45 | + # internal projects | |
46 | + # | |
47 | + group.projects.public_and_internal_only | |
48 | + end | |
49 | + end | |
50 | + else | |
51 | + # Not authenticated | |
52 | + # | |
53 | + # Return only: | |
54 | + # public projects | |
55 | + group.projects.public_only | |
56 | + end | |
57 | + end | |
58 | + | |
59 | + def all_projects | |
60 | + # TODO: implement | |
61 | + raise 'Not implemented yet' | |
62 | + end | |
63 | +end | ... | ... |
app/models/ability.rb
... | ... | @@ -184,7 +184,7 @@ class Ability |
184 | 184 | def group_abilities user, group |
185 | 185 | rules = [] |
186 | 186 | |
187 | - if user.admin? || group.users.include?(user) || Projects::CollectService.new.execute(user, group: group).any? | |
187 | + if user.admin? || group.users.include?(user) || ProjectsFinder.new.execute(user, group: group).any? | |
188 | 188 | rules << :read_group |
189 | 189 | end |
190 | 190 | ... | ... |
app/services/filtering_service.rb
... | ... | @@ -1,138 +0,0 @@ |
1 | -# FilteringService class | |
2 | -# | |
3 | -# Used to filter Issues and MergeRequests collections by set of params | |
4 | -# | |
5 | -# Arguments: | |
6 | -# klass - actual class like Issue or MergeRequest | |
7 | -# current_user - which user use | |
8 | -# params: | |
9 | -# scope: 'created-by-me' or 'assigned-to-me' or 'all' | |
10 | -# state: 'open' or 'closed' or 'all' | |
11 | -# group_id: integer | |
12 | -# project_id: integer | |
13 | -# milestone_id: integer | |
14 | -# assignee_id: integer | |
15 | -# search: string | |
16 | -# label_name: string | |
17 | -# sort: string | |
18 | -# | |
19 | -class FilteringService | |
20 | - attr_accessor :klass, :current_user, :params | |
21 | - | |
22 | - def execute(klass, current_user, params) | |
23 | - @klass = klass | |
24 | - @current_user = current_user | |
25 | - @params = params | |
26 | - | |
27 | - items = init_collection | |
28 | - items = by_scope(items) | |
29 | - items = by_state(items) | |
30 | - items = by_group(items) | |
31 | - items = by_project(items) | |
32 | - items = by_search(items) | |
33 | - items = by_milestone(items) | |
34 | - items = by_assignee(items) | |
35 | - items = by_label(items) | |
36 | - items = sort(items) | |
37 | - end | |
38 | - | |
39 | - private | |
40 | - | |
41 | - def init_collection | |
42 | - table_name = klass.table_name | |
43 | - | |
44 | - if project | |
45 | - if project.public? || (current_user && current_user.can?(:read_project, project)) | |
46 | - project.send(table_name) | |
47 | - else | |
48 | - [] | |
49 | - end | |
50 | - elsif current_user && params[:authorized_only].presence | |
51 | - klass.of_projects(current_user.authorized_projects) | |
52 | - else | |
53 | - klass.of_projects(Project.accessible_to(current_user)) | |
54 | - end | |
55 | - end | |
56 | - | |
57 | - def by_scope(items) | |
58 | - case params[:scope] | |
59 | - when 'created-by-me', 'authored' then | |
60 | - items.where(author_id: current_user.id) | |
61 | - when 'all' then | |
62 | - items | |
63 | - when 'assigned-to-me' then | |
64 | - items.where(assignee_id: current_user.id) | |
65 | - else | |
66 | - raise 'You must specify default scope' | |
67 | - end | |
68 | - end | |
69 | - | |
70 | - def by_state(items) | |
71 | - case params[:state] | |
72 | - when 'closed' | |
73 | - items.closed | |
74 | - when 'all' | |
75 | - items | |
76 | - when 'opened' | |
77 | - items.opened | |
78 | - else | |
79 | - raise 'You must specify default state' | |
80 | - end | |
81 | - end | |
82 | - | |
83 | - def by_group(items) | |
84 | - if params[:group_id].present? | |
85 | - items = items.of_group(Group.find(params[:group_id])) | |
86 | - end | |
87 | - | |
88 | - items | |
89 | - end | |
90 | - | |
91 | - def by_project(items) | |
92 | - if params[:project_id].present? | |
93 | - items = items.of_projects(params[:project_id]) | |
94 | - end | |
95 | - | |
96 | - items | |
97 | - end | |
98 | - | |
99 | - def by_search(items) | |
100 | - if params[:search].present? | |
101 | - items = items.search(params[:search]) | |
102 | - end | |
103 | - | |
104 | - items | |
105 | - end | |
106 | - | |
107 | - def sort(items) | |
108 | - items.sort(params[:sort]) | |
109 | - end | |
110 | - | |
111 | - def by_milestone(items) | |
112 | - if params[:milestone_id].present? | |
113 | - items = items.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id])) | |
114 | - end | |
115 | - | |
116 | - items | |
117 | - end | |
118 | - | |
119 | - def by_assignee(items) | |
120 | - if params[:assignee_id].present? | |
121 | - items = items.where(assignee_id: (params[:assignee_id] == '0' ? nil : params[:assignee_id])) | |
122 | - end | |
123 | - | |
124 | - items | |
125 | - end | |
126 | - | |
127 | - def by_label(items) | |
128 | - if params[:label_name].present? | |
129 | - items = items.tagged_with(params[:label_name]) | |
130 | - end | |
131 | - | |
132 | - items | |
133 | - end | |
134 | - | |
135 | - def project | |
136 | - Project.where(id: params[:project_id]).first if params[:project_id].present? | |
137 | - end | |
138 | -end |
app/services/notes/load_service.rb
... | ... | @@ -1,20 +0,0 @@ |
1 | -module Notes | |
2 | - class LoadService < BaseService | |
3 | - def execute | |
4 | - target_type = params[:target_type] | |
5 | - target_id = params[:target_id] | |
6 | - | |
7 | - | |
8 | - @notes = case target_type | |
9 | - when "commit" | |
10 | - project.notes.for_commit_id(target_id).not_inline.fresh | |
11 | - when "issue" | |
12 | - project.issues.find(target_id).notes.inc_author.fresh | |
13 | - when "merge_request" | |
14 | - project.merge_requests.find(target_id).mr_and_commit_notes.inc_author.fresh | |
15 | - when "snippet" | |
16 | - project.snippets.find(target_id).notes.fresh | |
17 | - end | |
18 | - end | |
19 | - end | |
20 | -end |
app/services/projects/collect_service.rb
... | ... | @@ -1,69 +0,0 @@ |
1 | -# Projects::CollectService class | |
2 | -# | |
3 | -# Used to collect projects user has access to | |
4 | -# | |
5 | -module Projects | |
6 | - class CollectService | |
7 | - def execute(current_user, options) | |
8 | - group = options[:group] | |
9 | - | |
10 | - if group | |
11 | - group_projects(current_user, group) | |
12 | - else | |
13 | - all_projects(current_user) | |
14 | - end | |
15 | - end | |
16 | - | |
17 | - private | |
18 | - | |
19 | - def group_projects(current_user, group) | |
20 | - if current_user | |
21 | - if group.users.include?(current_user) | |
22 | - # User is group member | |
23 | - # | |
24 | - # Return ALL group projects | |
25 | - group.projects | |
26 | - else | |
27 | - projects_members = UsersProject.where( | |
28 | - project_id: group.projects, | |
29 | - user_id: current_user | |
30 | - ) | |
31 | - | |
32 | - if projects_members.any? | |
33 | - # User is a project member | |
34 | - # | |
35 | - # Return only: | |
36 | - # public projects | |
37 | - # internal projects | |
38 | - # joined projects | |
39 | - # | |
40 | - group.projects.where( | |
41 | - "projects.id IN (?) OR projects.visibility_level IN (?)", | |
42 | - projects_members.pluck(:project_id), | |
43 | - Project.public_and_internal_levels | |
44 | - ) | |
45 | - else | |
46 | - # User has no access to group or group projects | |
47 | - # | |
48 | - # Return only: | |
49 | - # public projects | |
50 | - # internal projects | |
51 | - # | |
52 | - group.projects.public_and_internal_only | |
53 | - end | |
54 | - end | |
55 | - else | |
56 | - # Not authenticated | |
57 | - # | |
58 | - # Return only: | |
59 | - # public projects | |
60 | - group.projects.public_only | |
61 | - end | |
62 | - end | |
63 | - end | |
64 | - | |
65 | - def all_projects | |
66 | - # TODO: implement | |
67 | - raise 'Not implemented yet' | |
68 | - end | |
69 | -end |
config/application.rb
... | ... | @@ -12,7 +12,7 @@ module Gitlab |
12 | 12 | # -- all .rb files in that directory are automatically loaded. |
13 | 13 | |
14 | 14 | # Custom directories with classes and modules you want to be autoloadable. |
15 | - config.autoload_paths += %W(#{config.root}/lib #{config.root}/app/models/concerns #{config.root}/app/models/project_services) | |
15 | + config.autoload_paths += %W(#{config.root}/lib #{config.root}/app/finders #{config.root}/app/models/concerns #{config.root}/app/models/project_services) | |
16 | 16 | |
17 | 17 | # Only load the plugins named here, in the order given (default is alphabetical). |
18 | 18 | # :all can be used as a placeholder for all plugins not explicitly named. |
... | ... | @@ -64,7 +64,7 @@ module Gitlab |
64 | 64 | config.assets.enabled = true |
65 | 65 | config.assets.paths << Emoji.images_path |
66 | 66 | config.assets.precompile << "emoji/*.png" |
67 | - | |
67 | + | |
68 | 68 | # Version of your assets, change this if you want to expire all your assets |
69 | 69 | config.assets.version = '1.0' |
70 | 70 | ... | ... |