Commit 645e8d470559b07a22164c55b76195a60fb8b37b

Authored by Dmitriy Zaporozhets
1 parent 0f473674

Move services for collecting items to Finders

Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
app/controllers/dashboard_controller.rb
@@ -53,13 +53,13 @@ class DashboardController &lt; ApplicationController @@ -53,13 +53,13 @@ class DashboardController &lt; ApplicationController
53 end 53 end
54 54
55 def merge_requests 55 def merge_requests
56 - @merge_requests = FilteringService.new.execute(MergeRequest, current_user, params) 56 + @merge_requests = MergeRequestsFinder.new.execute(current_user, params)
57 @merge_requests = @merge_requests.page(params[:page]).per(20) 57 @merge_requests = @merge_requests.page(params[:page]).per(20)
58 @merge_requests = @merge_requests.preload(:author, :target_project) 58 @merge_requests = @merge_requests.preload(:author, :target_project)
59 end 59 end
60 60
61 def issues 61 def issues
62 - @issues = FilteringService.new.execute(Issue, current_user, params) 62 + @issues = IssuesFinder.new.execute(current_user, params)
63 @issues = @issues.page(params[:page]).per(20) 63 @issues = @issues.page(params[:page]).per(20)
64 @issues = @issues.preload(:author, :project) 64 @issues = @issues.preload(:author, :project)
65 65
app/controllers/groups_controller.rb
@@ -47,13 +47,13 @@ class GroupsController &lt; ApplicationController @@ -47,13 +47,13 @@ class GroupsController &lt; ApplicationController
47 end 47 end
48 48
49 def merge_requests 49 def merge_requests
50 - @merge_requests = FilteringService.new.execute(MergeRequest, current_user, params) 50 + @merge_requests = MergeRequestsFinder.new.execute(current_user, params)
51 @merge_requests = @merge_requests.page(params[:page]).per(20) 51 @merge_requests = @merge_requests.page(params[:page]).per(20)
52 @merge_requests = @merge_requests.preload(:author, :target_project) 52 @merge_requests = @merge_requests.preload(:author, :target_project)
53 end 53 end
54 54
55 def issues 55 def issues
56 - @issues = FilteringService.new.execute(Issue, current_user, params) 56 + @issues = IssuesFinder.new.execute(current_user, params)
57 @issues = @issues.page(params[:page]).per(20) 57 @issues = @issues.page(params[:page]).per(20)
58 @issues = @issues.preload(:author, :project) 58 @issues = @issues.preload(:author, :project)
59 59
@@ -100,7 +100,7 @@ class GroupsController &lt; ApplicationController @@ -100,7 +100,7 @@ class GroupsController &lt; ApplicationController
100 end 100 end
101 101
102 def projects 102 def projects
103 - @projects ||= Projects::CollectService.new.execute(current_user, group: group) 103 + @projects ||= ProjectsFinder.new.execute(current_user, group: group)
104 end 104 end
105 105
106 def project_ids 106 def project_ids
app/controllers/projects/issues_controller.rb
@@ -121,7 +121,7 @@ class Projects::IssuesController &lt; Projects::ApplicationController @@ -121,7 +121,7 @@ class Projects::IssuesController &lt; Projects::ApplicationController
121 def issues_filtered 121 def issues_filtered
122 params[:scope] = 'all' if params[:scope].blank? 122 params[:scope] = 'all' if params[:scope].blank?
123 params[:state] = 'opened' if params[:state].blank? 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 end 125 end
126 126
127 # Since iids are implemented only in 6.1 127 # Since iids are implemented only in 6.1
app/controllers/projects/merge_requests_controller.rb
@@ -21,7 +21,7 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController @@ -21,7 +21,7 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController
21 params[:scope] = 'all' if params[:scope].blank? 21 params[:scope] = 'all' if params[:scope].blank?
22 params[:state] = 'opened' if params[:state].blank? 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 @merge_requests = @merge_requests.page(params[:page]).per(20) 25 @merge_requests = @merge_requests.page(params[:page]).per(20)
26 26
27 @sort = params[:sort].humanize 27 @sort = params[:sort].humanize
app/controllers/projects/notes_controller.rb
@@ -5,7 +5,7 @@ class Projects::NotesController &lt; Projects::ApplicationController @@ -5,7 +5,7 @@ class Projects::NotesController &lt; Projects::ApplicationController
5 before_filter :authorize_admin_note!, only: [:update, :destroy] 5 before_filter :authorize_admin_note!, only: [:update, :destroy]
6 6
7 def index 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 notes_json = { notes: [] } 10 notes_json = { notes: [] }
11 11
app/finders/README.md 0 → 100644
@@ -0,0 +1,24 @@ @@ -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
app/finders/base_finder.rb 0 → 100644
@@ -0,0 +1,137 @@ @@ -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
app/finders/issues_finder.rb 0 → 100644
@@ -0,0 +1,22 @@ @@ -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
app/finders/merge_requests_finder.rb 0 → 100644
@@ -0,0 +1,22 @@ @@ -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
app/finders/notes_finder.rb 0 → 100644
@@ -0,0 +1,17 @@ @@ -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
app/finders/projects_finder.rb 0 → 100644
@@ -0,0 +1,63 @@ @@ -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,7 +184,7 @@ class Ability
184 def group_abilities user, group 184 def group_abilities user, group
185 rules = [] 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 rules << :read_group 188 rules << :read_group
189 end 189 end
190 190
app/services/filtering_service.rb
@@ -1,138 +0,0 @@ @@ -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,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,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,7 +12,7 @@ module Gitlab
12 # -- all .rb files in that directory are automatically loaded. 12 # -- all .rb files in that directory are automatically loaded.
13 13
14 # Custom directories with classes and modules you want to be autoloadable. 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 # Only load the plugins named here, in the order given (default is alphabetical). 17 # Only load the plugins named here, in the order given (default is alphabetical).
18 # :all can be used as a placeholder for all plugins not explicitly named. 18 # :all can be used as a placeholder for all plugins not explicitly named.
@@ -64,7 +64,7 @@ module Gitlab @@ -64,7 +64,7 @@ module Gitlab
64 config.assets.enabled = true 64 config.assets.enabled = true
65 config.assets.paths << Emoji.images_path 65 config.assets.paths << Emoji.images_path
66 config.assets.precompile << "emoji/*.png" 66 config.assets.precompile << "emoji/*.png"
67 - 67 +
68 # Version of your assets, change this if you want to expire all your assets 68 # Version of your assets, change this if you want to expire all your assets
69 config.assets.version = '1.0' 69 config.assets.version = '1.0'
70 70