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 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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  
... ...
app/finders/README.md 0 → 100644
... ... @@ -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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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  
... ...