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.
@@ -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\">&lt;&lt; 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(' &nbsp; ').html_safe
260
+ end
261
+ end
262
+ end # link_to_brick
263
+
264
+ end
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 97
8
+ TINY = 99
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
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 (is_show_schema_list = (apartment_multitenant && Apartment::Tenant.current == ::Brick.default_schema)) &&
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
- elsif ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
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.97
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-25 00:00:00.000000000 Z
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