/
column.rb
112 lines (91 loc) · 3.27 KB
/
column.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# frozen_string_literal: true
# Copyright (c) 2012-2024, Schweizer Blasmusikverband. This file is part of
# hitobito_sbv and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito.
module TableDisplays
class Column
attr_reader :template, :table, :model_class, :ability
def initialize(ability, model_class:, table: nil)
@ability = ability
@model_class = model_class
@table = table
@template = table&.template
end
# Allows a column class to specify which database tables need to be joined for calculating the
# value
def required_model_joins(attr)
resolve_database_joins(attr)
end
# Allows a column class to specify which database columns need to be fetched for calculating the
# value
def required_model_attrs(_attr)
raise 'implement in subclass'
end
def value_for(object, attr, &block)
target, target_attr = resolve(object, attr)
if target.present? && target_attr.present?
if allowed?(target, target_attr, object, attr)
allowed_value_for(target, target_attr, &block)
else
I18n.t('global.not_allowed')
end
end
end
def label(attr)
model_class.human_attribute_name(attr)
end
# The column class may specify how to sort, by returning a SQL string. Default nil means the
# column is not sortable.
def sort_by(_attr)
nil
end
def render(attr)
raise 'implement in subclass, using `super do ... end`' unless block_given?
table.col(header(attr), data: { attribute_name: attr }) do |object|
value_for(object, attr) do |target, target_attr|
yield target, target_attr, object, attr
end
end
end
def header(attr)
if sort_by(attr).present?
table.sort_header(attr, label(attr))
else
label(attr)
end
end
protected
def required_permission(_attr)
raise 'implement in subclass'
end
def allowed?(object, attr, _original_object, _original_attr)
ability.can? required_permission(attr), object
end
# Recursively resolve nested attrs
def resolve(object, path)
return object, path unless path.to_s.include? '.'
relation, relation_path = path.to_s.split('.', 2)
resolve(object.try(relation), relation_path)
end
def resolve_database_joins(path, model_class = @model_class)
return {} unless path.to_s.include? '.'
relation, relation_path = path.to_s.split('.', 2)
relation_class = model_class.reflect_on_association(relation).class_name.constantize
{ relation => resolve_database_column(relation_path, relation_class) }
end
def resolve_database_column(path, model_class = @model_class)
return "#{model_class.table_name}.#{path}" unless path.to_s.include? '.'
relation, relation_path = path.to_s.split('.', 2)
relation_class = model_class.reflect_on_association(relation).class_name.constantize
resolve_database_column(relation_path, relation_class)
end
def allowed_value_for(target, target_attr, &block)
if block.present?
block.call(target, target_attr)
else
[target, target_attr]
end
end
end
end