brick 1.0.97 → 1.0.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/brick/extensions.rb +208 -191
- data/lib/brick/frameworks/rails/controller.rb +1 -1
- data/lib/brick/frameworks/rails/engine.rb +285 -197
- data/lib/brick/frameworks/rails/form_tags.rb +264 -0
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +7 -6
- metadata +3 -2
@@ -0,0 +1,264 @@
|
|
1
|
+
module Brick::Rails::FormTags
|
2
|
+
# Our super speedy grid
|
3
|
+
def brick_grid(relation, bt_descrip, sequence = nil, inclusions, exclusions,
|
4
|
+
cols, poly_cols, bts, hms_keys, hms_cols)
|
5
|
+
out = "<table id=\"headerTop\"></table>
|
6
|
+
<table id=\"#{relation.table_name}\" class=\"shadow\">
|
7
|
+
<thead><tr>"
|
8
|
+
pk = (klass = relation.klass).primary_key || []
|
9
|
+
pk = [pk] unless pk.is_a?(Array)
|
10
|
+
if pk.present?
|
11
|
+
out << "<th x-order=\"#{pk.join(',')}\"></th>"
|
12
|
+
end
|
13
|
+
|
14
|
+
col_keys ||= relation.columns.each_with_object([]) do |col, s|
|
15
|
+
col_name = col.name
|
16
|
+
next if inclusions&.exclude?(col_name) ||
|
17
|
+
(pk.include?(col_name) && [:integer, :uuid].include?(col.type) && !bts.key?(col_name)) ||
|
18
|
+
::Brick.config.metadata_columns.include?(col_name) || poly_cols.include?(col_name)
|
19
|
+
|
20
|
+
s << col_name
|
21
|
+
cols[col_name] = col
|
22
|
+
end
|
23
|
+
unless sequence # If no sequence is defined, start with all inclusions
|
24
|
+
cust_cols = klass._br_cust_cols
|
25
|
+
# HOT columns, kept as symbols
|
26
|
+
hots = klass._br_bt_descrip.keys.select { |k| bts.key?(k) }
|
27
|
+
sequence = col_keys + cust_cols.keys + hots + hms_keys.reject { |assoc_name| inclusions&.exclude?(assoc_name) }
|
28
|
+
end
|
29
|
+
sequence.reject! { |nm| exclusions.include?(nm) } if exclusions
|
30
|
+
out << sequence.each_with_object(+'') do |col_name, s|
|
31
|
+
if (col = cols[col_name]).is_a?(ActiveRecord::ConnectionAdapters::Column)
|
32
|
+
s << '<th'
|
33
|
+
s << " title=\"#{col.comment}\"" if col.respond_to?(:comment) && !col.comment.blank?
|
34
|
+
s << if (bt = bts[col_name])
|
35
|
+
# Allow sorting for any BT except polymorphics
|
36
|
+
"#{' x-order="' + bt.first.to_s + '"' unless bt[2]}>BT " +
|
37
|
+
bt[1].map { |bt_pair| bt_pair.first.bt_link(bt.first) }.join(' ')
|
38
|
+
else # Normal column
|
39
|
+
"#{' x-order="' + col_name + '"' if true}>#{col_name}"
|
40
|
+
end
|
41
|
+
elsif col # HM column
|
42
|
+
s << "<th#{' x-order="' + col_name + '"' if true}>#{col[2]} "
|
43
|
+
s << (col.first ? "#{col[3]}" : "#{link_to(col[3], send("#{col[1]._brick_index}_path"))}")
|
44
|
+
elsif cust_cols.key?(col_name) # Custom column
|
45
|
+
s << "<th x-order=\"#{col_name}\">#{col_name}"
|
46
|
+
elsif col_name.is_a?(Symbol) && (hot = bts[col_name]) # has_one :through
|
47
|
+
s << "<th x-order=\"#{hot.first.to_s}\">HOT " +
|
48
|
+
hot[1].map { |hot_pair| hot_pair.first.bt_link(col_name) }.join(' ')
|
49
|
+
hot[1].first
|
50
|
+
else # Bad column name!
|
51
|
+
s << "<th title=\"<< Unknown column >>\">#{col_name}"
|
52
|
+
end
|
53
|
+
s << '</th>'
|
54
|
+
end
|
55
|
+
out << "</tr></thead>
|
56
|
+
<tbody>"
|
57
|
+
# %%% Have once gotten this error with MSSQL referring to http://localhost:3000/warehouse/cold_room_temperatures__archive
|
58
|
+
# ActiveRecord::StatementTimeout in Warehouse::ColdRoomTemperatures_Archive#index
|
59
|
+
# TinyTds::Error: Adaptive Server connection timed out
|
60
|
+
# (After restarting the server it worked fine again.)
|
61
|
+
relation.each do |obj|
|
62
|
+
out << "<tr>\n"
|
63
|
+
out << "<td>#{link_to('⇛', send("#{klass._brick_index(:singular)}_path".to_sym,
|
64
|
+
pk.map { |pk_part| obj.send(pk_part.to_sym) }), { class: 'big-arrow' })}</td>\n" if pk.present?
|
65
|
+
sequence.each do |col_name|
|
66
|
+
val = obj.attributes[col_name]
|
67
|
+
out << '<td'
|
68
|
+
out << ' class=\"dimmed\"' unless cols.key?(col_name) || (cust_col = cust_cols[col_name]) ||
|
69
|
+
(col_name.is_a?(Symbol) && bts.key?(col_name)) # HOT
|
70
|
+
out << '>'
|
71
|
+
if (bt = bts[col_name])
|
72
|
+
if bt[2] # Polymorphic?
|
73
|
+
bt_class = obj.send("#{bt.first}_type")
|
74
|
+
base_class_underscored = (::Brick.existing_stis[bt_class] || bt_class).constantize.base_class._brick_index(:singular)
|
75
|
+
poly_id = obj.send("#{bt.first}_id")
|
76
|
+
out << link_to("#{bt_class} ##{poly_id}", send("#{base_class_underscored}_path".to_sym, poly_id)) if poly_id
|
77
|
+
else # BT or HOT
|
78
|
+
bt_class = bt[1].first.first
|
79
|
+
descrips = bt_descrip[bt.first][bt_class]
|
80
|
+
bt_id_col = if descrips.nil?
|
81
|
+
puts "Caught it in the act for obj / #{col_name}!"
|
82
|
+
elsif descrips.length == 1
|
83
|
+
[obj.class.reflect_on_association(bt.first)&.foreign_key]
|
84
|
+
else
|
85
|
+
descrips.last
|
86
|
+
end
|
87
|
+
bt_txt = bt_class.brick_descrip(
|
88
|
+
# 0..62 because Postgres column names are limited to 63 characters
|
89
|
+
obj, descrips[0..-2].map { |id| obj.send(id.last[0..62]) }, bt_id_col
|
90
|
+
)
|
91
|
+
bt_txt ||= "<span class=\"orphan\"><< Orphaned ID: #{val} >></span>" if val
|
92
|
+
bt_id = bt_id_col&.map { |id_col| obj.respond_to?(id_sym = id_col.to_sym) ? obj.send(id_sym) : id_col }
|
93
|
+
out << (bt_id&.first ? link_to(bt_txt, send("#{bt_class.base_class._brick_index(:singular)}_path".to_sym, bt_id)) : bt_txt || '')
|
94
|
+
end
|
95
|
+
elsif (hms_col = hms_cols[col_name])
|
96
|
+
if hms_col.length == 1
|
97
|
+
out << hms_col.first
|
98
|
+
else
|
99
|
+
klass = (col = cols[col_name])[1]
|
100
|
+
if col[2] == 'HO'
|
101
|
+
descrips = bt_descrip[col_name.to_sym][klass]
|
102
|
+
if (ho_id = (ho_id_col = descrips.last).map { |id_col| obj.send(id_col.to_sym) })&.first
|
103
|
+
ho_txt = klass.brick_descrip(obj, descrips[0..-2].map { |id| obj.send(id.last[0..62]) }, ho_id_col)
|
104
|
+
out << link_to(ho_txt, send("#{klass.base_class._brick_index(:singular)}_path".to_sym, ho_id))
|
105
|
+
end
|
106
|
+
else
|
107
|
+
if (ct = obj.send(hms_col[1].to_sym)&.to_i)&.positive?
|
108
|
+
out << "#{link_to("#{ct || 'View'} #{hms_col.first}",
|
109
|
+
send("#{klass._brick_index}_path".to_sym,
|
110
|
+
hms_col[2].each_with_object({}) { |v, s| s[v.first] = v.last.is_a?(String) ? v.last : obj.send(v.last) })
|
111
|
+
)}\n"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
elsif (col = cols[col_name]).is_a?(ActiveRecord::ConnectionAdapters::Column)
|
116
|
+
binding.pry if col.is_a?(Array)
|
117
|
+
col_type = col&.sql_type == 'geography' ? col.sql_type : col&.type
|
118
|
+
out << display_value(col_type || col&.sql_type, val).to_s
|
119
|
+
elsif cust_col
|
120
|
+
data = cust_col.first.map { |cc_part| obj.send(cc_part.last) }
|
121
|
+
cust_txt = klass.brick_descrip(cust_col[-2], data)
|
122
|
+
if (link_id = obj.send(cust_col.last[1]) if cust_col.last)
|
123
|
+
out << link_to(cust_txt, send("#{cust_col.last.first._brick_index(:singular)}_path", link_id))
|
124
|
+
else
|
125
|
+
out << (cust_txt || '')
|
126
|
+
end
|
127
|
+
else # Bad column name!
|
128
|
+
out << '?'
|
129
|
+
end
|
130
|
+
out << '</td>'
|
131
|
+
end
|
132
|
+
out << '</tr>'
|
133
|
+
end
|
134
|
+
out << " </tbody>
|
135
|
+
</table>
|
136
|
+
"
|
137
|
+
out.html_safe
|
138
|
+
end # brick_grid
|
139
|
+
|
140
|
+
# Recursively render UL and LI to choose columns to include
|
141
|
+
def pick_api(model, is_last = nil, visited = [])
|
142
|
+
out = +''
|
143
|
+
if visited.empty?
|
144
|
+
out << '<span id="apiToggle">API buffet</span><div id="apiBuffet" style="display: none;">'
|
145
|
+
out << '<textarea id="apiCode" cols="100"></textarea>'
|
146
|
+
out << '<input type="button" id="apiRender" value="Render">'
|
147
|
+
end
|
148
|
+
out << "\n#{indent = ' ' * visited.length}<ul>"
|
149
|
+
model.column_names.each { |col_name| out << "\n#{indent} <li class=\"apiColName\" x-nm=\"#{visited.map { |v| "#{v.last}." }.join}#{col_name}\">#{col_name}</li>" }
|
150
|
+
unless is_last || visited.length == 2
|
151
|
+
model.reflect_on_all_associations.each_with_object({}) do |v, s|
|
152
|
+
next if v.macro == :has_many || v.polymorphic?
|
153
|
+
|
154
|
+
out << "\n#{indent} <li><b>#{v.name}</b>#{pick_api(v.klass, visited.map(&:first).include?(v.klass),
|
155
|
+
visited.dup << [v.klass, v.name]
|
156
|
+
)}"
|
157
|
+
out << "\n#{indent} </li>"
|
158
|
+
s[v.name] = nil
|
159
|
+
end
|
160
|
+
end
|
161
|
+
out << "\n#{indent}</ul>"
|
162
|
+
if visited.empty?
|
163
|
+
out << '</div><script>[... document.getElementsByClassName("apiColName")].forEach(function (li) {li.addEventListener("click", apiColClick);});'
|
164
|
+
out << "
|
165
|
+
var colList = [];
|
166
|
+
var apiBuffet = document.getElementById(\"apiBuffet\");
|
167
|
+
var apiCode = document.getElementById(\"apiCode\");
|
168
|
+
function apiColClick(evt) {
|
169
|
+
apiCode.innerHTML += this.getAttribute(\"x-nm\") + \",\\n\";
|
170
|
+
}
|
171
|
+
document.getElementById(\"apiToggle\").addEventListener(\"click\", function () {
|
172
|
+
apiBuffet.style.display = (apiBuffet.style.display === \"block\" ? \"none\" : \"block\");
|
173
|
+
});
|
174
|
+
// Cheap and cheerful way to render a list of columns just for the demo
|
175
|
+
document.getElementById(\"apiRender\").addEventListener(\"click\", function () {
|
176
|
+
var changecolList = apiCode.innerHTML.substring(0, apiCode.innerHTML.length - 2);
|
177
|
+
location.href = changeout(location.href, \"_brick_api\", changecolList);
|
178
|
+
});
|
179
|
+
</script>
|
180
|
+
"
|
181
|
+
end
|
182
|
+
out
|
183
|
+
end
|
184
|
+
|
185
|
+
def link_to_brick(*args, **kwargs)
|
186
|
+
return unless ::Brick.config.mode == :on
|
187
|
+
|
188
|
+
text = ((args.first.is_a?(String) || args.first.is_a?(Proc)) && args.shift) || args[1]
|
189
|
+
text = text.call if text.is_a?(Proc)
|
190
|
+
klass_or_obj = ((args.first.is_a?(ActiveRecord::Relation) ||
|
191
|
+
args.first.is_a?(ActiveRecord::Base) ||
|
192
|
+
args.first.is_a?(Class)) &&
|
193
|
+
args.first) ||
|
194
|
+
@_brick_model
|
195
|
+
# If not provided, do a best-effort to automatically determine the resource class or object
|
196
|
+
filter_parts = []
|
197
|
+
klass_or_obj ||= begin
|
198
|
+
klass, sti_type = ::Brick.ctrl_to_klass(controller_path)
|
199
|
+
if klass
|
200
|
+
type_col = klass.inheritance_column # Usually 'type'
|
201
|
+
filter_parts << "#{type_col}=#{sti_type}" if sti_type && klass.column_names.include?(type_col)
|
202
|
+
path_params = request.path_parameters.dup
|
203
|
+
path_params.delete(:controller)
|
204
|
+
path_params.delete(:action)
|
205
|
+
pk = (klass.primary_key || ActiveRecord::Base.primary_key).to_sym
|
206
|
+
# Used to also have this but it's a bit too permissive to identify a primary key: (path_params.length == 1 && path_params.values.first) ||
|
207
|
+
if ((id = (path_params[pk] || path_params[:id] || path_params["#{klass.name.underscore}_id".to_sym])) && (obj = klass.find_by(pk => id))) ||
|
208
|
+
(['show', 'edit', 'update', 'destroy'].include?(action_name) && (obj = klass.first))
|
209
|
+
obj
|
210
|
+
else
|
211
|
+
# %%% If there is a HMT that refers to some ___id then try to identify an appropriate filter
|
212
|
+
# %%% If there is a polymorphic association that might relate to stuff in the path_params,
|
213
|
+
# try to identify an appropriate ___able_id and ___able_type filter
|
214
|
+
((klass.column_names - [pk.to_s]) & path_params.keys.map(&:to_s)).each do |path_param|
|
215
|
+
filter_parts << "#{path_param}=#{path_params[path_param.to_sym]}"
|
216
|
+
end
|
217
|
+
klass
|
218
|
+
end
|
219
|
+
end
|
220
|
+
rescue
|
221
|
+
end
|
222
|
+
if klass_or_obj
|
223
|
+
if klass_or_obj.is_a?(ActiveRecord::Relation)
|
224
|
+
klass_or_obj.where_values_hash.each do |whr|
|
225
|
+
filter_parts << "#{whr.first}=#{whr.last}" if whr.last && !whr.last.is_a?(Array)
|
226
|
+
end
|
227
|
+
klass_or_obj = klass_or_obj.klass
|
228
|
+
type_col = klass_or_obj.inheritance_column
|
229
|
+
if klass_or_obj.column_names.include?(type_col) && klass_or_obj.name != klass_or_obj.base_class.name
|
230
|
+
filter_parts << "#{type_col}=#{klass_or_obj.name}"
|
231
|
+
end
|
232
|
+
end
|
233
|
+
filter = "?#{filter_parts.join('&')}" if filter_parts.present?
|
234
|
+
if (klass_or_obj&.is_a?(Class) && klass_or_obj < ActiveRecord::Base) ||
|
235
|
+
(klass_or_obj&.is_a?(ActiveRecord::Base) && klass_or_obj.new_record? && (klass_or_obj = klass_or_obj.class))
|
236
|
+
path = (proc = kwargs[:index_proc]) ? proc.call(klass_or_obj) : "#{send("#{klass_or_obj.base_class._brick_index}_path")}#{filter}"
|
237
|
+
lt_args = [text || "Index for #{klass_or_obj.name.pluralize}", path]
|
238
|
+
else
|
239
|
+
# If there are multiple incoming parameters then last one is probably the actual ID, and first few might be some nested tree of stuff leading up to it
|
240
|
+
path = (proc = kwargs[:show_proc]) ? proc.call(klass_or_obj) : "#{send("#{klass_or_obj.class.base_class._brick_index(:singular)}_path", klass_or_obj)}#{filter}"
|
241
|
+
lt_args = [text || "Show this #{klass_or_obj.class.name}", path]
|
242
|
+
end
|
243
|
+
link_to(*lt_args, **kwargs)
|
244
|
+
else
|
245
|
+
# puts "Warning: link_to_brick could not find a class for \"#{controller_path}\" -- consider setting @_brick_model within that controller."
|
246
|
+
# if (hits = res_names.keys & instance_variables.map { |v| v.to_s[1..-1] }).present?
|
247
|
+
links = instance_variables.each_with_object(Hash.new { |h, k| h[k] = [] }) do |name, s|
|
248
|
+
iv_name = name.to_s[1..-1]
|
249
|
+
case (val = instance_variable_get(name))
|
250
|
+
when ActiveRecord::Relation
|
251
|
+
s[val.klass] << iv_name
|
252
|
+
when ActiveRecord::Base
|
253
|
+
s[val] << iv_name
|
254
|
+
end
|
255
|
+
end
|
256
|
+
if links.length == 1 # If there's only one match then use any text that was supplied
|
257
|
+
link_to_brick(text || links.first.last.join('/'), links.first.first, **kwargs)
|
258
|
+
else
|
259
|
+
links.map { |k, v| link_to_brick(v.join('/'), v, **kwargs) }.join(' ').html_safe
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end # link_to_brick
|
263
|
+
|
264
|
+
end
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
@@ -138,15 +138,16 @@ module Brick
|
|
138
138
|
def set_db_schema(params = nil)
|
139
139
|
# If Apartment::Tenant.current is not still the default (usually 'public') then an elevator has brought us into
|
140
140
|
# a different tenant. If so then don't allow schema navigation.
|
141
|
-
chosen = if
|
141
|
+
chosen = if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' &&
|
142
|
+
(current_schema = (ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2]
|
143
|
+
.split(',') - ['pg_catalog', 'pg_toast', 'heroku_ext']).first) &&
|
144
|
+
(is_show_schema_list = (apartment_multitenant && current_schema == ::Brick.default_schema)) &&
|
142
145
|
(schema = (params ? params['_brick_schema'] : ::Brick.default_schema)) &&
|
143
146
|
::Brick.db_schemas&.key?(schema)
|
144
147
|
Apartment::Tenant.switch!(schema)
|
145
148
|
schema
|
146
|
-
|
147
|
-
# Just return the current schema
|
148
|
-
current_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
|
149
|
-
(current_schema - ['pg_catalog', 'pg_toast', 'heroku_ext']).first
|
149
|
+
else
|
150
|
+
current_schema # Just return the current schema
|
150
151
|
end
|
151
152
|
[chosen == ::Brick.default_schema ? nil : chosen, is_show_schema_list]
|
152
153
|
end
|
@@ -1262,8 +1263,8 @@ module ActiveRecord
|
|
1262
1263
|
# entry in your .select().
|
1263
1264
|
# More information: https://discuss.rubyonrails.org/t/includes-and-select-for-joined-data/81640
|
1264
1265
|
def apply_column_aliases(relation)
|
1265
|
-
used_cols = {}
|
1266
1266
|
if (sel_vals = relation.select_values.map(&:to_s)).first == '_brick_eager_load'
|
1267
|
+
used_cols = {}
|
1267
1268
|
# Find and expand out all column names being used in select(...)
|
1268
1269
|
new_select_values = sel_vals.each_with_object([]) do |col, s|
|
1269
1270
|
next if col == '_brick_eager_load'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brick
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.99
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lorin Thwaits
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-12-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -216,6 +216,7 @@ files:
|
|
216
216
|
- lib/brick/frameworks/rails/controller.rb
|
217
217
|
- lib/brick/frameworks/rails/crosstab.brk
|
218
218
|
- lib/brick/frameworks/rails/engine.rb
|
219
|
+
- lib/brick/frameworks/rails/form_tags.rb
|
219
220
|
- lib/brick/frameworks/rspec.rb
|
220
221
|
- lib/brick/join_array.rb
|
221
222
|
- lib/brick/serializers/json.rb
|