Feature #29482 » 0001-Filters-for-Projects-page.patch
app/controllers/projects_controller.rb | ||
---|---|---|
33 | 33 |
helper :custom_fields |
34 | 34 |
helper :issues |
35 | 35 |
helper :queries |
36 |
include QueriesHelper |
|
36 | 37 |
helper :repositories |
37 | 38 |
helper :members |
38 | 39 |
helper :trackers |
... | ... | |
44 | 45 |
return |
45 | 46 |
end |
46 | 47 | |
47 |
scope = Project.visible.sorted |
|
48 |
retrieve_project_query |
|
49 |
scope = project_scope |
|
48 | 50 | |
49 | 51 |
respond_to do |format| |
50 | 52 |
format.html { |
51 |
unless params[:closed] |
|
52 |
scope = scope.active |
|
53 |
end |
|
54 | 53 |
@projects = scope.to_a |
55 | 54 |
} |
56 | 55 |
format.api { |
... | ... | |
257 | 256 |
# hide project in layout |
258 | 257 |
@project = nil |
259 | 258 |
end |
259 | ||
260 |
private |
|
261 | ||
262 |
# Returns the ProjectEntry scope for index |
|
263 |
def project_scope(options={}) |
|
264 |
@query.results_scope(options) |
|
265 |
end |
|
266 | ||
267 |
def retrieve_project_query |
|
268 |
retrieve_query(ProjectQuery, false) |
|
269 |
end |
|
260 | 270 |
end |
app/controllers/queries_controller.rb | ||
---|---|---|
126 | 126 |
@query.column_names = nil if params[:default_columns] |
127 | 127 |
@query.sort_criteria = (params[:query] && params[:query][:sort_criteria]) || @query.sort_criteria |
128 | 128 |
@query.name = params[:query] && params[:query][:name] |
129 |
if User.current.allowed_to?(:manage_public_queries, @query.project) || User.current.admin? |
|
129 |
if User.current.allowed_to?(:manage_public_queries, @query.project) || User.current.admin? || (@query.type == 'ProjectQuery' && User.current.allowed_to?(:manage_public_queries, @query.project, :global => true))
|
|
130 | 130 |
@query.visibility = (params[:query] && params[:query][:visibility]) || Query::VISIBILITY_PRIVATE |
131 | 131 |
@query.role_ids = params[:query] && params[:query][:role_ids] |
132 | 132 |
else |
... | ... | |
156 | 156 |
redirect_to _time_entries_path(@project, nil, options) |
157 | 157 |
end |
158 | 158 | |
159 |
def redirect_to_project_query(options) |
|
160 |
redirect_to projects_path(options) |
|
161 |
end |
|
162 | ||
159 | 163 |
# Returns the Query subclass, IssueQuery by default |
160 | 164 |
# for compatibility with previous behaviour |
161 | 165 |
def query_class |
app/models/project_query.rb | ||
---|---|---|
1 |
# frozen_string_literal: true |
|
2 | ||
3 |
# Redmine - project management software |
|
4 |
# Copyright (C) 2006-2017 Jean-Philippe Lang |
|
5 |
# |
|
6 |
# This program is free software; you can redistribute it and/or |
|
7 |
# modify it under the terms of the GNU General Public License |
|
8 |
# as published by the Free Software Foundation; either version 2 |
|
9 |
# of the License, or (at your option) any later version. |
|
10 |
# |
|
11 |
# This program is distributed in the hope that it will be useful, |
|
12 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 |
# GNU General Public License for more details. |
|
15 |
# |
|
16 |
# You should have received a copy of the GNU General Public License |
|
17 |
# along with this program; if not, write to the Free Software |
|
18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
19 | ||
20 |
class ProjectQuery < Query |
|
21 | ||
22 |
self.queried_class = Project |
|
23 | ||
24 |
self.available_columns = [] |
|
25 | ||
26 |
def initialize(attributes=nil, *args) |
|
27 |
super attributes |
|
28 |
self.filters ||= { 'status' => {:operator => "=", :values => ['1']} } |
|
29 |
end |
|
30 | ||
31 |
def initialize_available_filters |
|
32 |
add_available_filter "status", |
|
33 |
:type => :list, :values => lambda { project_statuses_values } |
|
34 |
add_available_filter "name", :type => :text |
|
35 |
add_available_filter "description", :type => :text |
|
36 |
add_available_filter "is_public", |
|
37 |
:type => :list, |
|
38 |
:values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] |
|
39 |
add_available_filter "created_on", :type => :date_past |
|
40 |
end |
|
41 | ||
42 |
def available_columns |
|
43 |
[] |
|
44 |
end |
|
45 | ||
46 |
def base_scope |
|
47 |
Project.visible.where(statement) |
|
48 |
end |
|
49 | ||
50 |
def results_scope(options={}) |
|
51 |
order_option = [group_by_sort_order, (options[:order] || sort_clause)].flatten.reject(&:blank?) |
|
52 | ||
53 |
order_option << "#{Project.table_name}.id ASC" |
|
54 |
scope = base_scope. |
|
55 |
order(order_option). |
|
56 |
joins(joins_for_order_statement(order_option.join(','))) |
|
57 | ||
58 |
if has_custom_field_column? |
|
59 |
scope = scope.preload(:custom_values) |
|
60 |
end |
|
61 | ||
62 |
if has_column?(:parent_id) |
|
63 |
scope = scope.preload(:parent) |
|
64 |
end |
|
65 | ||
66 |
scope |
|
67 |
end |
|
68 |
end |
app/models/query.rb | ||
---|---|---|
716 | 716 |
end |
717 | 717 | |
718 | 718 |
def columns |
719 |
return [] if available_columns.empty? |
|
719 | 720 |
# preserve the column_names order |
720 | 721 |
cols = (has_default_columns? ? default_columns_names : column_names).collect do |name| |
721 | 722 |
available_columns.find { |col| col.name == name } |
app/views/projects/_sidebar.html.erb | ||
---|---|---|
1 |
<%= render_sidebar_queries(ProjectQuery, @project) %> |
|
2 |
<%= call_hook(:view_projects_sidebar_queries_bottom) %> |
app/views/projects/index.html.erb | ||
---|---|---|
1 |
<% content_for :header_tags do %> |
|
2 |
<%= auto_discovery_link_tag(:atom, {:action => 'index', :format => 'atom', :key => User.current.rss_key}) %> |
|
3 |
<% end %> |
|
4 | ||
5 | 1 |
<div class="contextual"> |
6 | 2 |
<%= form_tag({}, :method => :get) do %> |
7 |
<label for="closed"> |
|
8 |
<%= check_box_tag 'closed', 1, params[:closed], :onchange => "this.form.submit();" %> |
|
9 |
<%= l(:label_show_closed_projects) %> |
|
10 |
</label> |
|
11 | 3 |
<% end %> |
12 | 4 |
<%= render_project_action_links %> |
13 | 5 |
</div> |
14 | 6 | |
15 |
<h2><%= l(:label_project_plural) %></h2>
|
|
7 |
<h2><%= @query.new_record? ? l(:label_project_plural) : @query.name %></h2>
|
|
16 | 8 | |
17 |
<div id="projects-index"> |
|
18 |
<%= render_project_hierarchy(@projects) %> |
|
19 |
</div> |
|
9 |
<%= form_tag(projects_path(@project, nil), :method => :get, :id => 'query_form') do %> |
|
10 |
<%= render :partial => 'queries/query_form' %> |
|
11 |
<% end %> |
|
12 | ||
13 |
<% if @query.valid? %> |
|
14 |
<% if @projects.empty? %> |
|
15 |
<p class="nodata"><%= l(:label_no_data) %></p> |
|
16 |
<% else %> |
|
17 |
<div id="projects-index"> |
|
18 |
<%= render_project_hierarchy(@projects) %> |
|
19 |
</div> |
|
20 |
<% end %> |
|
21 |
<% end %> |
|
20 | 22 | |
21 | 23 |
<% if User.current.logged? %> |
22 | 24 |
<p style="text-align:right;"> |
... | ... | |
24 | 26 |
</p> |
25 | 27 |
<% end %> |
26 | 28 | |
29 |
<% content_for :sidebar do %> |
|
30 |
<%= render :partial => 'projects/sidebar' %> |
|
31 |
<% end %> |
|
32 | ||
27 | 33 |
<% other_formats_links do |f| %> |
28 | 34 |
<%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %> |
29 | 35 |
<% end %> |
app/views/queries/_form.html.erb | ||
---|---|---|
7 | 7 |
<p><label for="query_name"><%=l(:field_name)%></label> |
8 | 8 |
<%= text_field 'query', 'name', :size => 80 %></p> |
9 | 9 | |
10 |
<% if User.current.admin? || User.current.allowed_to?(:manage_public_queries, @query.project) %> |
|
10 |
<% if User.current.admin? || |
|
11 |
User.current.allowed_to?(:manage_public_queries, @query.project) || |
|
12 |
@query.type == 'ProjectQuery' && User.current.allowed_to?(:manage_public_queries, @query.project, :global => true) %> |
|
11 | 13 |
<p><label><%=l(:field_visible)%></label> |
12 | 14 |
<label class="block"><%= radio_button 'query', 'visibility', Query::VISIBILITY_PRIVATE %> <%= l(:label_visibility_private) %></label> |
13 | 15 |
<label class="block"><%= radio_button 'query', 'visibility', Query::VISIBILITY_PUBLIC %> <%= l(:label_visibility_public) %></label> |
14 |
<label class="block"><%= radio_button 'query', 'visibility', Query::VISIBILITY_ROLES %> <%= l(:label_visibility_roles) %>:</label> |
|
15 |
<% Role.givable.sorted.each do |role| %> |
|
16 |
<label class="block role-visibility"><%= check_box_tag 'query[role_ids][]', role.id, @query.roles.include?(role), :id => nil %> <%= role.name %></label> |
|
16 |
<% unless @query.type == 'ProjectQuery' %> |
|
17 |
<label class="block"><%= radio_button 'query', 'visibility', Query::VISIBILITY_ROLES %> <%= l(:label_visibility_roles) %>:</label> |
|
18 |
<% Role.givable.sorted.each do |role| %> |
|
19 |
<label class="block role-visibility"><%= check_box_tag 'query[role_ids][]', role.id, @query.roles.include?(role), :id => nil %> <%= role.name %></label> |
|
20 |
<% end %> |
|
21 |
<%= hidden_field_tag 'query[role_ids][]', '' %> |
|
17 | 22 |
<% end %> |
18 |
<%= hidden_field_tag 'query[role_ids][]', '' %> |
|
19 | 23 |
</p> |
20 | 24 |
<% end %> |
21 | 25 | |
22 |
<p><label for="query_is_for_all"><%=l(:field_is_for_all)%></label> |
|
23 |
<%= check_box_tag 'query_is_for_all', 1, @query.project.nil?, :class => (User.current.admin? ? '' : 'disable-unless-private') %></p> |
|
26 |
<% unless @query.type == 'ProjectQuery' %> |
|
27 |
<p><label for="query_is_for_all"><%=l(:field_is_for_all)%></label> |
|
28 |
<%= check_box_tag 'query_is_for_all', 1, @query.project.nil?, :class => (User.current.admin? ? '' : 'disable-unless-private') %></p> |
|
29 |
<% end %> |
|
24 | 30 | |
25 | 31 |
<fieldset id="options"><legend><%= l(:label_options) %></legend> |
26 | 32 |
<p><label for="query_default_columns"><%=l(:label_default_columns)%></label> |
... | ... | |
28 | 34 |
:data => {:disables => "#columns, .block_columns input"} %></p> |
29 | 35 | |
30 | 36 |
<% unless params[:gantt] %> |
31 |
<p><label for="query_group_by"><%= l(:field_group_by) %></label> |
|
32 |
<%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %></p> |
|
37 |
<p><label for="query_group_by"><%= l(:field_group_by) %></label>
|
|
38 |
<%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %></p>
|
|
33 | 39 | |
34 |
<p class="block_columns"><label><%= l(:button_show) %></label> |
|
35 |
<%= available_block_columns_tags(@query) %></p> |
|
40 |
<% unless @query.available_block_columns.empty? %> |
|
41 |
<p class="block_columns"><label><%= l(:button_show) %></label> |
|
42 |
<%= available_block_columns_tags(@query) %></p> |
|
43 |
<% end %> |
|
36 | 44 | |
37 |
<p><label><%= l(:label_total_plural) %></label> |
|
38 |
<%= available_totalable_columns_tags(@query) %></p> |
|
45 |
<% unless @query.available_totalable_columns.empty? %> |
|
46 |
<p class="totable_columns"><label><%= l(:label_total_plural) %></label> |
|
47 |
<%= available_totalable_columns_tags(@query) %></p> |
|
48 |
<% end %> |
|
39 | 49 |
<% else %> |
40 | 50 |
<p><label><%= l(:button_show) %></label> |
41 | 51 |
<%= hidden_field_tag 'query[draw_relations]', '0' %> |
... | ... | |
54 | 64 |
</fieldset> |
55 | 65 | |
56 | 66 |
<% unless params[:gantt] %> |
57 |
<fieldset><legend><%= l(:label_sort) %></legend> |
|
67 |
<fieldset id="sort"><legend><%= l(:label_sort) %></legend>
|
|
58 | 68 |
<% 3.times do |i| %> |
59 | 69 |
<%= content_tag(:span, "#{i+1}:", :class => 'query_sort_criteria_count')%> |
60 | 70 |
<%= label_tag "query_sort_criteria_attribute_" + i.to_s, |
app/views/queries/_query_form.html.erb | ||
---|---|---|
11 | 11 |
</div> |
12 | 12 |
</fieldset> |
13 | 13 | |
14 |
<fieldset id="options" class="collapsible collapsed"> |
|
15 |
<legend onclick="toggleFieldset(this);" class="icon icon-collapsed"><%= l(:label_options) %></legend> |
|
16 |
<div style="display: none;"> |
|
17 |
<table> |
|
18 |
<tr> |
|
19 |
<td class="field"><%= l(:field_column_names) %></td> |
|
20 |
<td><%= render_query_columns_selection(@query) %></td> |
|
21 |
</tr> |
|
22 |
<% if @query.groupable_columns.any? %> |
|
23 |
<tr> |
|
24 |
<td class="field"><label for='group_by'><%= l(:field_group_by) %></label></td> |
|
25 |
<td><%= group_by_column_select_tag(@query) %></td> |
|
26 |
</tr> |
|
27 |
<% end %> |
|
28 |
<% if @query.available_block_columns.any? %> |
|
29 |
<tr> |
|
30 |
<td class="field"><%= l(:button_show) %></td> |
|
31 |
<td><%= available_block_columns_tags(@query) %></td> |
|
32 |
</tr> |
|
33 |
<% end %> |
|
34 |
<% if @query.available_totalable_columns.any? %> |
|
35 |
<tr> |
|
36 |
<td><%= l(:label_total_plural) %></td> |
|
37 |
<td><%= available_totalable_columns_tags(@query) %></td> |
|
38 |
</tr> |
|
39 |
<% end %> |
|
40 |
</table> |
|
41 |
</div> |
|
42 |
</fieldset> |
|
14 |
<% if @query.available_columns.any? %> |
|
15 |
<fieldset id="options" class="collapsible collapsed"> |
|
16 |
<legend onclick="toggleFieldset(this);" class="icon icon-collapsed"><%= l(:label_options) %></legend> |
|
17 |
<div style="display: none;"> |
|
18 |
<table> |
|
19 |
<% if @query.available_columns.any? %> |
|
20 |
<tr> |
|
21 |
<td class="field"><%= l(:field_column_names) %></td> |
|
22 |
<td><%= render_query_columns_selection(@query) %></td> |
|
23 |
</tr> |
|
24 |
<% end %> |
|
25 |
<% if @query.groupable_columns.any? %> |
|
26 |
<tr> |
|
27 |
<td class="field"><label for='group_by'><%= l(:field_group_by) %></label></td> |
|
28 |
<td><%= group_by_column_select_tag(@query) %></td> |
|
29 |
</tr> |
|
30 |
<% end %> |
|
31 |
<% if @query.available_block_columns.any? %> |
|
32 |
<tr> |
|
33 |
<td class="field"><%= l(:button_show) %></td> |
|
34 |
<td><%= available_block_columns_tags(@query) %></td> |
|
35 |
</tr> |
|
36 |
<% end %> |
|
37 |
<% if @query.available_totalable_columns.any? %> |
|
38 |
<tr> |
|
39 |
<td><%= l(:label_total_plural) %></td> |
|
40 |
<td><%= available_totalable_columns_tags(@query) %></td> |
|
41 |
</tr> |
|
42 |
<% end %> |
|
43 |
</table> |
|
44 |
</div> |
|
45 |
</fieldset> |
|
46 |
<% end %> |
|
43 | 47 |
</div> |
44 | 48 | |
45 | 49 |
<p class="buttons"> |
config/locales/en.yml | ||
---|---|---|
977 | 977 |
label_completed_versions: Completed versions |
978 | 978 |
label_search_for_watchers: Search for watchers to add |
979 | 979 |
label_session_expiration: Session expiration |
980 |
label_show_closed_projects: View closed projects |
|
981 | 980 |
label_status_transitions: Status transitions |
982 | 981 |
label_fields_permissions: Fields permissions |
983 | 982 |
label_readonly: Read-only |
public/stylesheets/application.css | ||
---|---|---|
649 | 649 |
-moz-column-count: auto; |
650 | 650 |
-moz-column-width: 400px; |
651 | 651 |
-moz-column-gap : 0.5rem; |
652 |
margin-bottom: 1.2em; |
|
652 | 653 |
} |
653 | 654 |
#projects-index li.root ul.projects { border-left: 3px solid #e0e0e0; padding-left:1em;} |
654 | 655 |
#projects-index ul.projects li.root { |
test/functional/queries_controller_test.rb | ||
---|---|---|
69 | 69 |
assert_response 404 |
70 | 70 |
end |
71 | 71 | |
72 |
def test_new_should_not_render_show_inline_columns_option_for_query_without_available_inline_columns |
|
73 |
@request.session[:user_id] = 1 |
|
74 |
get :new, :params => { |
|
75 |
:type => 'ProjectQuery' |
|
76 |
} |
|
77 | ||
78 |
assert_response :success |
|
79 |
assert_select 'p[class=?]', 'block_columns', 0 |
|
80 |
end |
|
81 | ||
82 |
def test_new_should_not_render_show_totals_option_for_query_without_totable_columns |
|
83 |
@request.session[:user_id] = 1 |
|
84 |
get :new, :params => { |
|
85 |
:type => 'ProjectQuery' |
|
86 |
} |
|
87 | ||
88 |
assert_response :success |
|
89 |
assert_select 'p[class=?]', 'totables_columns', 0 |
|
90 |
end |
|
91 | ||
72 | 92 |
def test_new_time_entry_query |
73 | 93 |
@request.session[:user_id] = 2 |
74 | 94 |
get :new, :params => { |
... | ... | |
77 | 97 |
} |
78 | 98 |
assert_response :success |
79 | 99 |
assert_select 'input[name=type][value=?]', 'TimeEntryQuery' |
100 |
assert_select 'p[class=?]', 'totable_columns', 1 |
|
101 |
assert_select 'p[class=?]', 'block_columns', 0 |
|
102 |
end |
|
103 | ||
104 |
def test_new_project_query_for_projects |
|
105 |
@request.session[:user_id] = 1 |
|
106 |
get :new, :params => { |
|
107 |
:type => 'ProjectQuery' |
|
108 |
} |
|
109 |
assert_response :success |
|
110 |
assert_select 'input[name=type][value=?]', 'ProjectQuery' |
|
111 |
end |
|
112 | ||
113 |
def test_new_project_query_should_not_render_roles_visibility_options |
|
114 |
@request.session[:user_id] = 1 |
|
115 |
get :new, :params => { |
|
116 |
:type => 'ProjectQuery' |
|
117 |
} |
|
118 | ||
119 |
assert_response :success |
|
120 |
assert_select 'input[id=?]', 'query_visibility_0', 1 |
|
121 |
assert_select 'input[id=?]', 'query_visibility_2', 1 |
|
122 |
assert_select 'input[id=?]', 'query_visibility_1', 0 |
|
123 |
end |
|
124 | ||
125 |
def test_new_project_query_should_not_render_for_all_projects_option |
|
126 |
@request.session[:user_id] = 1 |
|
127 |
get :new, :params => { |
|
128 |
:type => 'ProjectQuery' |
|
129 |
} |
|
130 | ||
131 |
assert_response :success |
|
132 |
assert_select 'input[name=?]', 'for_all_projects', 0 |
|
80 | 133 |
end |
81 | 134 | |
82 | 135 |
def test_new_time_entry_query_should_select_spent_time_from_main_menu |
... | ... | |
441 | 494 |
assert q.valid? |
442 | 495 |
end |
443 | 496 | |
497 |
def test_create_public_project_query |
|
498 |
@request.session[:user_id] = 2 |
|
499 | ||
500 |
q = new_record(ProjectQuery) do |
|
501 |
post :create, :params => { |
|
502 |
:type => 'ProjectQuery', |
|
503 |
:default_columns => '1', |
|
504 |
:f => ["status"], |
|
505 |
:op => { |
|
506 |
"status" => "=" |
|
507 |
}, |
|
508 |
:v => { |
|
509 |
"status" => ['1'] |
|
510 |
}, |
|
511 |
:query => { |
|
512 |
"name" => "test_new_project_public_query", "visibility" => "2" |
|
513 |
} |
|
514 |
} |
|
515 |
end |
|
516 | ||
517 |
assert_redirected_to :controller => 'projects', :action => 'index', :query_id => q.id |
|
518 | ||
519 |
assert q.is_public? |
|
520 |
assert q.valid? |
|
521 |
end |
|
522 | ||
444 | 523 |
def test_edit_global_public_query |
445 | 524 |
@request.session[:user_id] = 1 |
446 | 525 |
get :edit, :params => { |
test/unit/project_query_test.rb | ||
---|---|---|
1 |
# frozen_string_literal: true |
|
2 | ||
3 |
# Redmine - project management software |
|
4 |
# Copyright (C) 2006-2017 Jean-Philippe Lang |
|
5 |
# |
|
6 |
# This program is free software; you can redistribute it and/or |
|
7 |
# modify it under the terms of the GNU General Public License |
|
8 |
# as published by the Free Software Foundation; either version 2 |
|
9 |
# of the License, or (at your option) any later version. |
|
10 |
# |
|
11 |
# This program is distributed in the hope that it will be useful, |
|
12 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 |
# GNU General Public License for more details. |
|
15 |
# |
|
16 |
# You should have received a copy of the GNU General Public License |
|
17 |
# along with this program; if not, write to the Free Software |
|
18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
19 | ||
20 |
require File.expand_path('../../test_helper', __FILE__) |
|
21 | ||
22 |
class ProjectQueryTest < ActiveSupport::TestCase |
|
23 |
fixtures :projects, :users, |
|
24 |
:members, :roles, :member_roles, |
|
25 |
:issue_categories, :enumerations, |
|
26 |
:groups_users, |
|
27 |
:enabled_modules |
|
28 | ||
29 |
def test_filter_values_be_arrays |
|
30 |
q = ProjectQuery.new |
|
31 |
assert_nil q.project |
|
32 | ||
33 |
q.available_filters.each do |name, filter| |
|
34 |
values = filter.values |
|
35 |
assert (values.nil? || values.is_a?(Array)), |
|
36 |
"#values for #{name} filter returned a #{values.class.name}" |
|
37 |
end |
|
38 |
end |
|
39 | ||
40 |
def test_project_statuses_filter_should_return_project_statuses |
|
41 |
query = ProjectQuery.new(:name => '_') |
|
42 |
query.filters = {'status' => {:operator => '=', :values => []}} |
|
43 | ||
44 |
values = query.available_filters['status'][:values] |
|
45 |
assert_equal ['active', 'closed'], values.map(&:first) |
|
46 |
assert_equal ['1', '5'], values.map(&:second) |
|
47 | ||
48 |
end |
|
49 |
end |