-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
dashboard_generator.rb
168 lines (141 loc) · 4.32 KB
/
dashboard_generator.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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
require "rails/generators/named_base"
module Administrate
module Generators
class DashboardGenerator < Rails::Generators::NamedBase
ATTRIBUTE_TYPE_MAPPING = {
boolean: "Field::Boolean",
date: "Field::Date",
datetime: "Field::DateTime",
enum: "Field::Select",
float: "Field::Number",
integer: "Field::Number",
time: "Field::Time",
text: "Field::Text",
string: "Field::String",
uuid: "Field::String"
}
ATTRIBUTE_OPTIONS_MAPPING = {
# procs must be defined in one line!
enum: {searchable: false,
collection: ->(field) { field.resource.class.send(field.attribute.to_s.pluralize).keys }},
float: {decimals: 2}
}
DEFAULT_FIELD_TYPE = "Field::String.with_options(searchable: false)"
COLLECTION_ATTRIBUTE_LIMIT = 4
READ_ONLY_ATTRIBUTES = %w[id created_at updated_at]
class_option(
:namespace,
type: :string,
desc: "Namespace where the admin dashboards live",
default: "admin"
)
source_root File.expand_path("../templates", __FILE__)
def create_dashboard_definition
scope = regular_class_path.join("/")
template(
"dashboard.rb.erb",
Rails.root.join("app/dashboards/#{scope}/#{file_name}_dashboard.rb")
)
end
def create_resource_controller
scope = "#{namespace}/#{regular_class_path.join('/')}"
destination = Rails.root.join(
"app/controllers/#{scope}/#{file_name.pluralize}_controller.rb"
)
template("controller.rb.erb", destination)
end
private
def namespace
options[:namespace]
end
def attributes
attrs = (
klass.reflections.keys +
klass.columns.map(&:name) -
redundant_attributes
)
primary_key = attrs.delete(klass.primary_key)
created_at = attrs.delete("created_at")
updated_at = attrs.delete("updated_at")
[
primary_key,
*attrs.sort,
created_at,
updated_at
].compact
end
def form_attributes
attributes - READ_ONLY_ATTRIBUTES
end
def redundant_attributes
klass.reflections.keys.flat_map do |relationship|
redundant_attributes_for(relationship)
end.compact
end
def redundant_attributes_for(relationship)
case association_type(relationship)
when "Field::Polymorphic"
[relationship + "_id", relationship + "_type"]
when "Field::BelongsTo"
relationship + "_id"
end
end
def field_type(attribute)
type = column_type_for_attribute(attribute.to_s)
if type
ATTRIBUTE_TYPE_MAPPING.fetch(type, DEFAULT_FIELD_TYPE) +
options_string(ATTRIBUTE_OPTIONS_MAPPING.fetch(type, {}))
else
association_type(attribute)
end
end
def column_type_for_attribute(attr)
if enum_column?(attr)
:enum
else
column_types(attr)
end
end
def enum_column?(attr)
klass.respond_to?(:defined_enums) &&
klass.defined_enums.key?(attr)
end
def column_types(attr)
klass.columns.find { |column| column.name == attr }.try(:type)
end
def association_type(attribute)
relationship = klass.reflections[attribute.to_s]
if relationship.has_one?
"Field::HasOne"
elsif relationship.collection?
"Field::HasMany"
elsif relationship.polymorphic?
"Field::Polymorphic"
else
"Field::BelongsTo"
end
end
def klass
@klass ||= Object.const_get(class_name)
end
def options_string(options)
if options.any?
".with_options(#{inspect_hash_as_ruby(options)})"
else
""
end
end
def inspect_hash_as_ruby(hash)
hash.map do |key, value|
v_str = value.respond_to?(:call) ? proc_string(value) : value.inspect
"#{key}: #{v_str}"
end.join(", ")
end
def proc_string(value)
source = value.source_location
proc_string = IO.readlines(source.first)[source.second - 1]
proc_string[/->[^}]*} | (lambda|proc).*end/x]
end
end
end
end