brick 1.0.16 → 1.0.19

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 30eeeb5fb81003f790234885589b29f227b7f4f2d498accb07da5cd8ea425084
4
- data.tar.gz: 1841386c2750c2aef744a7ee555b649e2fc0298367684d261faf64297b8430c0
3
+ metadata.gz: 57a1d4e3774c90984cfc435d9ed87e42b74c364b01fd7ced7df740f5005b935e
4
+ data.tar.gz: 8222088ef88ca48cd90144c8503638644195f2d593ff95d7b2e3530304ac6fb2
5
5
  SHA512:
6
- metadata.gz: 64c5747800aaa5469f2ff2b0be3ae5056fa383c6d246e7aff15b19eb4a588e15e72f5d1ecb384879b3b7f175cab28961fb4984e11d5994085b215f442d722389
7
- data.tar.gz: ec3418092244d97b3006086aaf0484ee1ab4342cf2c16716239f7127c8e23056dc25e1714e51523bdb8f87fb6b88c322dabefb096fe7bf0b1f695568b636d4b8
6
+ metadata.gz: 2b785c34e1a21563232ae54f6af25989fe02b73f92eb1327b27d6f21e80b7e353a5995305c4c1188dda885465a196f87fb5979a13c3e6a0219d4895dc59593dd
7
+ data.tar.gz: bd2018d48dad15f51d7a87e39d2710bde9a5becbe969e741004bf730f57faecbd84a81a2f75cfdd60c2ff48f239fe6c5c8d3f47b4b2f79e355b7f100f9bb7943
data/lib/brick/config.rb CHANGED
@@ -66,12 +66,12 @@ module Brick
66
66
  end
67
67
 
68
68
  # Skip creating a has_many association for these
69
- def skip_hms
70
- @mutex.synchronize { @skip_hms }
69
+ def exclude_hms
70
+ @mutex.synchronize { @exclude_hms }
71
71
  end
72
72
 
73
- def skip_hms=(skips)
74
- @mutex.synchronize { @skip_hms = skips }
73
+ def exclude_hms=(skips)
74
+ @mutex.synchronize { @exclude_hms = skips }
75
75
  end
76
76
 
77
77
  # Associations to treat as a has_one
@@ -108,7 +108,7 @@ module Brick
108
108
  end
109
109
 
110
110
  def exclude_tables
111
- @mutex.synchronize { @exclude_tables }
111
+ @mutex.synchronize { @exclude_tables || [] }
112
112
  end
113
113
 
114
114
  def exclude_tables=(value)
@@ -137,7 +137,6 @@ module ActiveRecord
137
137
  alias _brick_find_sti_class find_sti_class
138
138
  def find_sti_class(type_name)
139
139
  if ::Brick.sti_models.key?(type_name)
140
- # puts ['X', self.name, type_name].inspect
141
140
  _brick_find_sti_class(type_name)
142
141
  else
143
142
  # This auto-STI is more of a brute-force approach, building modules where needed
@@ -149,7 +148,7 @@ module ActiveRecord
149
148
  if ::Brick.config.sti_namespace_prefixes&.key?("::#{module_name}::") ||
150
149
  ::Brick.config.sti_namespace_prefixes&.key?("#{module_name}::")
151
150
  _brick_find_sti_class(type_name)
152
- elsif File.exists?(candidate_file = Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb'))
151
+ elsif File.exist?(candidate_file = Rails.root.join('app/models' + module_prefixes.map(&:underscore).join('/') + '.rb'))
153
152
  _brick_find_sti_class(type_name) # Find this STI class normally
154
153
  else
155
154
  # Build missing prefix modules if they don't yet exist
@@ -243,10 +242,10 @@ class Object
243
242
 
244
243
  # Adjust for STI if we know of a base model for the requested model name
245
244
  table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil))
246
- base_model.table_name
247
- else
248
- ActiveSupport::Inflector.pluralize(singular_table_name)
249
- end
245
+ base_model.table_name
246
+ else
247
+ ActiveSupport::Inflector.pluralize(singular_table_name)
248
+ end
250
249
 
251
250
  # Maybe, just maybe there's a database table that will satisfy this need
252
251
  if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(m) })
@@ -454,7 +453,7 @@ class Object
454
453
 
455
454
  if model.primary_key
456
455
  code << " def show\n"
457
- code << " @#{singular_table_name} = #{model.name}.find(params[:id].split(','))\n"
456
+ code << (find_by_id = " @#{singular_table_name} = #{model.name}.find(params[:id].split(','))\n")
458
457
  code << " end\n"
459
458
  self.define_method :show do
460
459
  ::Brick.set_db_schema(params)
@@ -463,9 +462,36 @@ class Object
463
462
  end
464
463
 
465
464
  # By default, views get marked as read-only
466
- unless (relation = relations[model.table_name]).key?(:isView)
467
- code << " # (Define :new, :create, :edit, :update, and :destroy)\n"
468
- # Get column names for params from relations[model.table_name][:cols].keys
465
+ unless false # model.readonly # (relation = relations[model.table_name]).key?(:isView)
466
+ code << " # (Define :new, :create)\n"
467
+
468
+ if model.primary_key
469
+ is_need_params = true
470
+ # code << " # (Define :edit, and :destroy)\n"
471
+ code << " def update\n"
472
+ code << find_by_id
473
+ params_name = "#{singular_table_name}_params"
474
+ code << " @#{singular_table_name}.update(#{params_name})\n"
475
+ code << " end\n"
476
+ self.define_method :update do
477
+ ::Brick.set_db_schema(params)
478
+ instance_variable_set("@#{singular_table_name}".to_sym, (obj = model.find(params[:id].split(','))))
479
+ obj = obj.first if obj.is_a?(Array)
480
+ obj.send(:update, send(params_name = params_name.to_sym))
481
+ end
482
+ end
483
+
484
+ if is_need_params
485
+ code << "private\n"
486
+ code << " def params\n"
487
+ code << " params.require(:#{singular_table_name}).permit(#{model.columns_hash.keys.map { |c| c.to_sym.inspect }.join(', ')})\n"
488
+ code << " end\n"
489
+ self.define_method(params_name) do
490
+ params.require(singular_table_name.to_sym).permit(model.columns_hash.keys)
491
+ end
492
+ private params_name
493
+ # Get column names for params from relations[model.table_name][:cols].keys
494
+ end
469
495
  end
470
496
  code << "end # #{class_name}\n\n"
471
497
  end # class definition
@@ -495,9 +521,9 @@ module ActiveRecord::ConnectionHandling
495
521
  end
496
522
 
497
523
  def _brick_reflect_tables
498
- if (relations = ::Brick.relations).empty?
499
- # Only for Postgres? (Doesn't work in sqlite3)
500
- # puts ActiveRecord::Base.connection.execute("SELECT current_setting('SEARCH_PATH')").to_a.inspect
524
+ if (relations = ::Brick.relations).empty?
525
+ # Only for Postgres? (Doesn't work in sqlite3)
526
+ # puts ActiveRecord::Base.connection.execute("SELECT current_setting('SEARCH_PATH')").to_a.inspect
501
527
 
502
528
  schema_sql = 'SELECT NULL AS table_schema;'
503
529
  case ActiveRecord::Base.connection.adapter_name
@@ -587,6 +613,25 @@ module ActiveRecord::ConnectionHandling
587
613
  end
588
614
  end
589
615
 
616
+ # # Add unique OIDs
617
+ # if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
618
+ # ActiveRecord::Base.execute_sql(
619
+ # "SELECT c.oid, n.nspname, c.relname
620
+ # FROM pg_catalog.pg_namespace AS n
621
+ # INNER JOIN pg_catalog.pg_class AS c ON n.oid = c.relnamespace
622
+ # WHERE c.relkind IN ('r', 'v')"
623
+ # ).each do |r|
624
+ # next if ['pg_catalog', 'information_schema', ''].include?(r['nspname']) ||
625
+ # ['ar_internal_metadata', 'schema_migrations'].include?(r['relname'])
626
+ # relation = relations.fetch(r['relname'], nil)
627
+ # if relation
628
+ # (relation[:oid] ||= {})[r['nspname']] = r['oid']
629
+ # else
630
+ # puts "Where is #{r['nspname']} #{r['relname']} ?"
631
+ # end
632
+ # end
633
+ # end
634
+
590
635
  case ActiveRecord::Base.connection.adapter_name
591
636
  when 'PostgreSQL', 'Mysql2'
592
637
  sql = ActiveRecord::Base.send(:sanitize_sql_array, [
@@ -629,10 +674,12 @@ module ActiveRecord::ConnectionHandling
629
674
  puts "\nClasses that can be built from views:"
630
675
  views.keys.each { |k| puts ActiveSupport::Inflector.singularize(k).camelize }
631
676
  end
632
- # pp relations; nil
633
677
 
634
- # relations.keys.each { |k| ActiveSupport::Inflector.singularize(k).camelize.constantize }
635
- # Layout table describes permissioned hierarchy throughout
678
+ # Try to load the initializer pretty danged early
679
+ if File.exist?(brick_initialiser = Rails.root.join('config/initializers/brick.rb'))
680
+ load brick_initialiser
681
+ ::Brick.load_additional_references
682
+ end
636
683
  end
637
684
  end
638
685
 
@@ -677,7 +724,7 @@ module Brick
677
724
  missing << fk[0] unless relations.key?(fk[0])
678
725
  missing << primary_table unless is_class || relations.key?(primary_table)
679
726
  unless missing.empty?
680
- tables = relations.reject { |k, v| v.fetch(:isView, nil) }.keys.sort
727
+ tables = relations.reject { |_k, v| v.fetch(:isView, nil) }.keys.sort
681
728
  puts "Brick: Additional reference #{fk.inspect} refers to non-existent #{'table'.pluralize(missing.length)} #{missing.join(' and ')}. (Available tables include #{tables.join(', ')}.)"
682
729
  return
683
730
  end
@@ -686,7 +733,7 @@ module Brick
686
733
  puts "Brick: Additional reference #{fk.inspect} refers to non-existent column #{fk[1]}. (Columns present in #{fk[0]} are #{columns.join(', ')}.)"
687
734
  return
688
735
  end
689
- if (redundant = bts.find { |k, v| v[:inverse]&.fetch(:inverse_table, nil) == fk[0] && v[:fk] == fk[1] && v[:inverse_table] == primary_table })
736
+ if (redundant = bts.find { |_k, v| v[:inverse]&.fetch(:inverse_table, nil) == fk[0] && v[:fk] == fk[1] && v[:inverse_table] == primary_table })
690
737
  if is_class && !redundant.last.key?(:class)
691
738
  redundant.last[:primary_class] = primary_class # Round out this BT so it can find the proper :source for a HMT association that references an STI subclass
692
739
  else
@@ -708,19 +755,19 @@ module Brick
708
755
  # assoc_bt[:inverse_of] = primary_class.reflect_on_all_associations.find { |a| a.foreign_key == bt[1] }
709
756
  end
710
757
 
711
- unless is_class || ::Brick.config.skip_hms&.any? { |skip| fk[0] == skip[0] && fk[1] == skip[1] && primary_table == skip[2] }
712
- cnstr_name = "hm_#{cnstr_name}"
713
- if (assoc_hm = hms.fetch(cnstr_name, nil))
714
- assoc_hm[:fk] = assoc_hm[:fk].is_a?(String) ? [assoc_hm[:fk], fk[1]] : assoc_hm[:fk].concat(fk[1])
715
- assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
716
- assoc_hm[:inverse] = assoc_bt
717
- else
718
- assoc_hm = hms[cnstr_name] = { is_bt: false, fk: fk[1], assoc_name: fk[0], alternate_name: bt_assoc_name, inverse_table: fk[0], inverse: assoc_bt }
719
- hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
720
- hm_counts[fk[0]] = hm_counts.fetch(fk[0]) { 0 } + 1
721
- end
722
- assoc_bt[:inverse] = assoc_hm
758
+ return if is_class || ::Brick.config.exclude_hms&.any? { |exclusion| fk[0] == exclusion[0] && fk[1] == exclusion[1] && primary_table == exclusion[2] }
759
+
760
+ cnstr_name = "hm_#{cnstr_name}"
761
+ if (assoc_hm = hms.fetch(cnstr_name, nil))
762
+ assoc_hm[:fk] = assoc_hm[:fk].is_a?(String) ? [assoc_hm[:fk], fk[1]] : assoc_hm[:fk].concat(fk[1])
763
+ assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
764
+ assoc_hm[:inverse] = assoc_bt
765
+ else
766
+ assoc_hm = hms[cnstr_name] = { is_bt: false, fk: fk[1], assoc_name: fk[0], alternate_name: bt_assoc_name, inverse_table: fk[0], inverse: assoc_bt }
767
+ hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
768
+ hm_counts[fk[0]] = hm_counts.fetch(fk[0]) { 0 } + 1
723
769
  end
770
+ assoc_bt[:inverse] = assoc_hm
724
771
  # hms[cnstr_name] << { is_bt: false, fk: fk[1], assoc_name: fk[0], alternate_name: bt_assoc_name, inverse_table: fk[0] }
725
772
  end
726
773
  end
@@ -32,7 +32,7 @@ module Brick
32
32
  ::Brick.additional_references = app.config.brick.fetch(:additional_references, nil)
33
33
 
34
34
  # Skip creating a has_many association for these
35
- ::Brick.skip_hms = app.config.brick.fetch(:skip_hms, nil)
35
+ ::Brick.exclude_hms = app.config.brick.fetch(:exclude_hms, nil)
36
36
 
37
37
  # Has one relationships
38
38
  ::Brick.has_ones = app.config.brick.fetch(:has_ones, nil)
@@ -51,14 +51,11 @@ module Brick
51
51
  # Need to return true if we can fill in the blanks for a missing one
52
52
  # args will be something like: ["index", ["categories"]]
53
53
  model = args[1].map(&:camelize).join('::').singularize.constantize
54
- if (
55
- is_template_exists = model && (
56
- ['index', 'show'].include?(args.first) || # Everything has index and show
57
- # Only CRU stuff has create / update / destroy
58
- (!model.is_view? && ['new', 'create', 'edit', 'update', 'destroy'].include?(args.first))
59
- )
60
- )
61
- instance_variable_set(:@_brick_model, model)
54
+ if is_template_exists = model && (
55
+ ['index', 'show'].include?(args.first) || # Everything has index and show
56
+ # Only CRU stuff has create / update / destroy
57
+ (!model.is_view? && ['new', 'create', 'edit', 'update', 'destroy'].include?(args.first))
58
+ ) && instance_variable_set(:@_brick_model, model)
62
59
  end
63
60
  end
64
61
  is_template_exists
@@ -76,33 +73,32 @@ module Brick
76
73
  bts, hms = ::Brick.get_bts_and_hms(@_brick_model)
77
74
  # Mark has_manys that go to an associative ("join") table so that they are skipped in the UI,
78
75
  # as well as any possible polymorphic associations
79
- skip_hms = {}
76
+ exclude_hms = {}
80
77
  associatives = hms.each_with_object({}) do |hmt, s|
81
78
  if (through = hmt.last.options[:through])
82
- skip_hms[through] = nil
79
+ exclude_hms[through] = nil
83
80
  s[hmt.first] = hms[through] # End up with a hash of HMT names pointing to join-table associations
84
81
  elsif hmt.last.inverse_of.nil?
85
82
  puts "SKIPPING #{hmt.last.name.inspect}"
86
83
  # %%% If we don't do this then below associative.name will find that associative is nil
87
- skip_hms[hmt.last.name] = nil
84
+ exclude_hms[hmt.last.name] = nil
88
85
  end
89
86
  end
90
87
 
91
- schema_options = ::Brick.db_schemas.each_with_object(+'') { |v, s| s << "<option value=\"#{v}\">#{v}</option>" }.html_safe
92
88
  hms_columns = +'' # Used for 'index'
93
89
  hms_headers = hms.each_with_object([]) do |hm, s|
94
- next if skip_hms.key?(hm.last.name)
90
+ next if exclude_hms.key?((hm_assoc = hm.last).name)
95
91
 
96
92
  if args.first == 'index'
97
- hm_fk_name = if hm.last.options[:through]
98
- associative = associatives[hm.last.name]
93
+ hm_fk_name = if hm_assoc.options[:through]
94
+ associative = associatives[hm_assoc.name]
99
95
  "'#{associative.name}.#{associative.foreign_key}'"
100
96
  else
101
- hm.last.foreign_key
97
+ hm_assoc.foreign_key
102
98
  end
103
- hms_columns << if hm.last.macro == :has_many
99
+ hms_columns << if hm_assoc.macro == :has_many
104
100
  "<td>
105
- <%= link_to \"#\{#{obj_name}.#{hm.first}.count\} #{hm.first}\", #{hm.last.klass.name.underscore.pluralize}_path({ #{hm_fk_name}: #{obj_name}.#{pk} }) unless #{obj_name}.#{hm.first}.count.zero? %>
101
+ <%= link_to \"#\{#{obj_name}.#{hm.first}.count\} #{hm.first}\", #{hm_assoc.klass.name.underscore.pluralize}_path({ #{hm_fk_name}: #{obj_name}.#{pk} }) unless #{obj_name}.#{hm.first}.count.zero? %>
106
102
  </td>\n"
107
103
  else # has_one
108
104
  "<td>
@@ -110,9 +106,15 @@ module Brick
110
106
  </td>\n"
111
107
  end
112
108
  end
113
- s << [hm.last, "H#{hm.last.macro == :has_one ? 'O' : 'M'}#{'T' if hm.last.options[:through]} #{hm.first}"]
109
+ s << [hm_assoc, "H#{hm_assoc.macro == :has_one ? 'O' : 'M'}#{'T' if hm_assoc.options[:through]} #{hm.first}"]
114
110
  end
115
-
111
+ end
112
+ if @_brick_model
113
+ schema_options = ::Brick.db_schemas.each_with_object(+'') { |v, s| s << "<option value=\"#{v}\">#{v}</option>" }.html_safe
114
+ # %%% If we are not auto-creating controllers (or routes) then omit by default, and if enabled anyway, such as in a development
115
+ # environment or whatever, then get either the controllers or routes list instead
116
+ table_options = (::Brick.relations.keys - ::Brick.config.exclude_tables)
117
+ .each_with_object(+'') { |v, s| s << "<option value=\"#{v.underscore.pluralize}\">#{v}</option>" }.html_safe
116
118
  css = "<style>
117
119
  table {
118
120
  border-collapse: collapse;
@@ -155,15 +157,43 @@ table tbody tr.active-row {
155
157
  }
156
158
 
157
159
  a.show-arrow {
160
+ font-size: 1.5em;
161
+ text-decoration: none;
162
+ }
163
+ a.big-arrow {
158
164
  font-size: 2.5em;
159
165
  text-decoration: none;
160
166
  }
161
- </style>"
167
+ .wide-input {
168
+ display: block;
169
+ overflow: hidden;
170
+ }
171
+ .wide-input input[type=text] {
172
+ width: 100%;
173
+ }
174
+ .dimmed {
175
+ background-color: #C0C0C0;
176
+ }
177
+ input[type=submit] {
178
+ background-color: #004998;
179
+ color: #FFF;
180
+ }
181
+ .right {
182
+ text-align: right;
183
+ }
184
+ </style>
185
+ <% def is_bcrypt?(val)
186
+ val.is_a?(String) && val.length == 60 && val.start_with?('$2a$')
187
+ end
188
+ def hide_bcrypt(val)
189
+ is_bcrypt?(val) ? '(hidden)' : val
190
+ end %>"
162
191
 
163
192
  script = "<script>
164
193
  var schemaSelect = document.getElementById(\"schema\");
194
+ var brickSchema;
165
195
  if (schemaSelect) {
166
- var brickSchema = changeout(location.href, \"_brick_schema\");
196
+ brickSchema = changeout(location.href, \"_brick_schema\");
167
197
  if (brickSchema) {
168
198
  [... document.getElementsByTagName(\"A\")].forEach(function (a) { a.href = changeout(a.href, \"_brick_schema\", brickSchema); });
169
199
  }
@@ -173,8 +203,39 @@ if (schemaSelect) {
173
203
  location.href = changeout(location.href, \"_brick_schema\", this.value);
174
204
  });
175
205
  }
206
+ [... document.getElementsByTagName(\"FORM\")].forEach(function (form) {
207
+ if (brickSchema)
208
+ form.action = changeout(form.action, \"_brick_schema\", brickSchema);
209
+ form.addEventListener('submit', function (ev) {
210
+ [... ev.target.getElementsByTagName(\"SELECT\")].forEach(function (select) {
211
+ if (select.value === \"^^^brick_NULL^^^\")
212
+ select.value = null;
213
+ });
214
+ return true;
215
+ });
216
+ });
217
+
218
+ var tblSelect = document.getElementById(\"tbl\");
219
+ if (tblSelect) {
220
+ tblSelect.value = changeout(location.href);
221
+ tblSelect.addEventListener(\"change\", function () {
222
+ var lhr = changeout(location.href, null, this.value);
223
+ if (brickSchema)
224
+ lhr = changeout(lhr, \"_brick_schema\", schemaSelect.value);
225
+ location.href = lhr;
226
+ });
227
+ }
228
+
176
229
  function changeout(href, param, value) {
177
230
  var hrefParts = href.split(\"?\");
231
+ if (param === undefined || param === null) {
232
+ hrefParts = hrefParts[0].split(\"://\");
233
+ var pathParts = hrefParts[hrefParts.length - 1].split(\"/\");
234
+ if (value === undefined)
235
+ return pathParts[1];
236
+ else
237
+ return hrefParts[0] + \"://\" + pathParts[0] + \"/\" + value;
238
+ }
178
239
  var params = hrefParts.length > 1 ? hrefParts[1].split(\"&\") : [];
179
240
  params = params.reduce(function (s, v) { var parts = v.split(\"=\"); s[parts[0]] = parts[1]; return s; }, {});
180
241
  if (value === undefined) return params[param];
@@ -188,10 +249,11 @@ function changeout(href, param, value) {
188
249
  "#{css}
189
250
  <p style=\"color: green\"><%= notice %></p>#{"
190
251
  <select id=\"schema\">#{schema_options}</select>" if ::Brick.db_schemas.length > 1}
252
+ <select id=\"tbl\">#{table_options}</select>
191
253
  <h1>#{model_name.pluralize}</h1>
192
254
  <% if @_brick_params&.present? %><h3>where <%= @_brick_params.each_with_object([]) { |v, s| s << \"#\{v.first\} = #\{v.last.inspect\}\" }.join(', ') %></h3><% end %>
193
255
  <table id=\"#{table_name}\">
194
- <thead><tr>#{"<th></th>" if pk }
256
+ <thead><tr>#{"<th></th>" if pk}
195
257
  <% bts = { #{bts.each_with_object([]) { |v, s| s << "#{v.first.inspect} => [#{v.last.first.inspect}, #{v.last[1].name}, #{v.last[1].primary_key.inspect}]"}.join(', ')} }
196
258
  @#{table_name}.columns.map(&:name).each do |col| %>
197
259
  <% next if col == '#{pk}' || ::Brick.config.metadata_columns.include?(col) %>
@@ -209,7 +271,7 @@ function changeout(href, param, value) {
209
271
  <tbody>
210
272
  <% @#{table_name}.each do |#{obj_name}| %>
211
273
  <tr>#{"
212
- <td><%= link_to '⇛', #{obj_name}_path(#{obj_name}.#{pk}), { class: 'show-arrow' } %></td>" if pk }
274
+ <td><%= link_to '⇛', #{obj_name}_path(#{obj_name}.#{pk}), { class: 'big-arrow' } %></td>" if pk}
213
275
  <% #{obj_name}.attributes.each do |k, val| %>
214
276
  <% next if k == '#{pk}' || ::Brick.config.metadata_columns.include?(k) %>
215
277
  <td>
@@ -218,9 +280,9 @@ function changeout(href, param, value) {
218
280
  # send(\"#\{bt_obj_class = bt[1].name.underscore\}_path\".to_sym, bt_obj.send(bt[1].primary_key.to_sym))
219
281
  # Otherwise we get stuff like:
220
282
  # ActionView::Template::Error (undefined method `vehicle_path' for #<ActionView::Base:0x0000000033a888>) %>
221
- <%= bt_obj = bt[1].find_by(bt.last => val); link_to(bt_obj.brick_descrip, send(\"#\{bt_obj_class = bt[1].name.underscore\}_path\".to_sym, bt_obj.send(bt[1].primary_key.to_sym))) if bt_obj %>
283
+ <%= bt_obj = bt[1].find_by(bt[2] => val); link_to(bt_obj.brick_descrip, send(\"#\{bt_obj_class = bt[1].name.underscore\}_path\".to_sym, bt_obj.send(bt[1].primary_key.to_sym))) if bt_obj %>
222
284
  <% else %>
223
- <%= val %>
285
+ <%= hide_bcrypt(val) %>
224
286
  <% end %>
225
287
  </td>
226
288
  <% end %>
@@ -233,47 +295,83 @@ function changeout(href, param, value) {
233
295
 
234
296
  #{"<hr><%= link_to \"New #{obj_name}\", new_#{obj_name}_path %>" unless @_brick_model.is_view?}
235
297
  #{script}"
236
- when 'show'
237
- "#{css}
238
- <p style=\"color: green\"><%= notice %></p>#{"
239
- <select id=\"schema\">#{schema_options}</select>" if ::Brick.db_schemas.length > 1}
240
- <h1>#{model_name}: <%= (obj = @#{obj_name}.first).brick_descrip %></h1>
241
- <%= link_to '(See all #{obj_name.pluralize})', #{table_name}_path %>
298
+ when 'show', 'update'
299
+ "#{css}
300
+ <p style=\"color: green\"><%= notice %></p>#{"
301
+ <select id=\"schema\">#{schema_options}</select>" if ::Brick.db_schemas.length > 1}
302
+ <select id=\"tbl\">#{table_options}</select>
303
+ <h1>#{model_name}: <%= (obj = @#{obj_name}&.first)&.brick_descrip || controller_name %></h1>
304
+ <%= link_to '(See all #{obj_name.pluralize})', #{table_name}_path %>
305
+ <% if obj %>
306
+ <%= # path_options = [obj.#{pk}]
307
+ # path_options << { '_brick_schema': } if
308
+ # url = send(:#{model_name.underscore}_path, obj.#{pk})
309
+ form_for(obj) do |f| %>
242
310
  <table>
243
311
  <% bts = { #{bts.each_with_object([]) { |v, s| s << "#{v.first.inspect} => [#{v.last.first.inspect}, #{v.last[1].name}, #{v.last[1].primary_key.inspect}]"}.join(', ')} }
244
312
  @#{obj_name}.first.attributes.each do |k, val| %>
245
313
  <tr>
246
314
  <% next if k == '#{pk}' || ::Brick.config.metadata_columns.include?(k) %>
247
315
  <th class=\"show-field\">
248
- <% if (bt = bts[k]) %>
249
- BT <%= \"#\{bt.first\}-\" unless bt[1].name.underscore == bt.first.to_s %><%= bt[1].name %>
316
+ <% if (bt = bts[k])
317
+ # Add a final member in this array with descriptive options to be used in <select> drop-downs
318
+ bt_name = bt[1].name
319
+ # %%% Only do this if the user has permissions to edit this bt field
320
+ if bt.length < 4
321
+ bt << (option_detail = [[\"(No #\{bt_name\} chosen)\", '^^^brick_NULL^^^']])
322
+ bt[1].order(:#{pk}).each { |obj| option_detail << [obj.brick_descrip, obj.#{pk}] }
323
+ end %>
324
+ BT <%= \"#\{bt.first\}-\" unless bt_name.underscore == bt.first.to_s %><%= bt_name %>
250
325
  <% else %>
251
326
  <%= k %>
252
327
  <% end %>
253
328
  </th>
254
329
  <td>
255
- <% if (bt = bts[k]) %>
256
- <%= bt_obj = bt[1].find_by(bt.last => val); link_to(bt_obj.brick_descrip, send(\"#\{bt_obj_class = bt[1].name.underscore\}_path\".to_sym, bt_obj.send(bt[1].primary_key.to_sym))) if bt_obj %>
257
- <% else %>
258
- <%= val %>
330
+ <% if (bt = bts[k]) # bt_obj.brick_descrip
331
+ html_options = { prompt: \"Select #\{bt_name\}\" }
332
+ html_options[:class] = 'dimmed' unless val %>
333
+ <%= f.select k.to_sym, bt[3], { value: val || '^^^brick_NULL^^^' }, html_options %>
334
+ <%= bt_obj = bt[1].find_by(bt[2] => val); link_to('⇛', send(\"#\{bt_obj_class = bt_name.underscore\}_path\".to_sym, bt_obj.send(bt[1].primary_key.to_sym)), { class: 'show-arrow' }) if bt_obj %>
335
+ <% else case #{model_name}.column_for_attribute(k).type
336
+ when :string, :text %>
337
+ <% if is_bcrypt?(val) # || .readonly? %>
338
+ <%= hide_bcrypt(val) %>
339
+ <% else %>
340
+ <div class=\"wide-input\"><%= f.text_field k.to_sym %></div>
341
+ <% end %>
342
+ <% when :boolean %>
343
+ <%= f.check_box k.to_sym %>
344
+ <% when :integer, :decimal, :float, :date, :datetime, :time, :timestamp
345
+ # What happens when keys are UUID?
346
+ # Postgres naturally uses the +uuid_generate_v4()+ function from the uuid-ossp extension
347
+ # If it's not yet enabled then: enable_extension 'uuid-ossp'
348
+ # ActiveUUID gem created a new :uuid type %>
349
+ <%= val %>
350
+ <% when :binary, :primary_key %>
351
+ <% end %>
259
352
  <% end %>
260
353
  </td>
261
354
  </tr>
262
- <% end %>
355
+ <% end %>
356
+ <tr><td colspan=\"2\" class=\"right\"><%= f.submit %></td></tr>
263
357
  </table>
358
+ <% end %>
264
359
 
265
360
  #{hms_headers.map do |hm|
266
361
  next unless (pk = hm.first.klass.primary_key)
267
362
  "<table id=\"#{hm_name = hm.first.name.to_s}\">
268
363
  <tr><th>#{hm.last}</th></tr>
269
- <% if (collection = @#{obj_name}.first.#{hm_name}).empty? %>
364
+ <% collection = @#{obj_name}.first.#{hm_name}
365
+ collection = collection.is_a?(ActiveRecord::Associations::CollectionProxy) ? collection.order(#{pk.inspect}) : [collection]
366
+ if collection.empty? %>
270
367
  <tr><td>(none)</td></tr>
271
368
  <% else %>
272
- <% collection.order(#{pk.inspect}).uniq.each do |#{hm_singular_name = hm_name.singularize.underscore}| %>
273
- <tr><td><%= link_to(#{hm_singular_name}.brick_descrip, #{hm_singular_name}_path(#{hm_singular_name}.#{pk})) %></td></tr>
369
+ <% collection.uniq.each do |#{hm_singular_name = hm_name.singularize.underscore}| %>
370
+ <tr><td><%= link_to(#{hm_singular_name}.brick_descrip, #{hm.first.klass.name.underscore}_path(#{hm_singular_name}.#{pk})) %></td></tr>
274
371
  <% end %>
275
372
  <% end %>
276
373
  </table>" end.join}
374
+ <% end %>
277
375
  #{script}"
278
376
 
279
377
  end
@@ -295,23 +393,9 @@ function changeout(href, param, value) {
295
393
  end
296
394
  end
297
395
 
298
- # Additional references (virtual foreign keys)
299
- if (ars = ::Brick.config.additional_references)
300
- ars.each do |fk|
301
- ::Brick._add_bt_and_hm(fk[0..2])
302
- end
303
- end
304
-
305
- # Find associative tables that can be set up for has_many :through
306
- ::Brick.relations.each do |_key, tbl|
307
- tbl_cols = tbl[:cols].keys
308
- fks = tbl[:fks].each_with_object({}) { |fk, s| s[fk.last[:fk]] = [fk.last[:assoc_name], fk.last[:inverse_table]] if fk.last[:is_bt]; s }
309
- # Aside from the primary key and the metadata columns created_at, updated_at, and deleted_at, if this table only has
310
- # foreign keys then it can act as an associative table and thus be used with has_many :through.
311
- if fks.length > 1 && (tbl_cols - fks.keys - (::Brick.config.metadata_columns || []) - (tbl[:pkey].values.first || [])).length.zero?
312
- fks.each { |fk| tbl[:hmt_fks][fk.first] = fk.last }
313
- end
314
- end
396
+ # Just in case it hadn't been done previously when we tried to load the brick initialiser,
397
+ # go make sure we've loaded additional references (virtual foreign keys).
398
+ ::Brick.load_additional_references
315
399
  end
316
400
  end
317
401
  end
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 16
8
+ TINY = 19
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
@@ -208,12 +208,12 @@ module Brick
208
208
  # Skip creating a has_many association for these
209
209
  # (Uses the same exact three-part format as would define an additional_reference)
210
210
  # @api public
211
- def skip_hms=(skips)
211
+ def exclude_hms=(skips)
212
212
  if skips
213
213
  skips = skips.call if skips.is_a?(Proc)
214
214
  skips = skips.to_a unless skips.is_a?(Array)
215
215
  skips = [skips] unless skips.empty? || skips.first.is_a?(Array)
216
- Brick.config.skip_hms = skips
216
+ Brick.config.exclude_hms = skips
217
217
  end
218
218
  end
219
219
 
@@ -244,6 +244,29 @@ module Brick
244
244
  Brick.config.sti_namespace_prefixes = snp
245
245
  end
246
246
 
247
+ # Load additional references (virtual foreign keys)
248
+ # This is attempted early if a brick initialiser file is found, and then again as a failsafe at the end of our engine's initialisation
249
+ # %%% Maybe look for differences the second time 'round and just add new stuff instead of entirely deferring
250
+ def load_additional_references
251
+ return if @_additional_references_loaded
252
+
253
+ if (ars = ::Brick.config.additional_references)
254
+ ars.each { |fk| ::Brick._add_bt_and_hm(fk[0..2]) }
255
+ @_additional_references_loaded = true
256
+ end
257
+
258
+ # Find associative tables that can be set up for has_many :through
259
+ ::Brick.relations.each do |_key, tbl|
260
+ tbl_cols = tbl[:cols].keys
261
+ fks = tbl[:fks].each_with_object({}) { |fk, s| s[fk.last[:fk]] = [fk.last[:assoc_name], fk.last[:inverse_table]] if fk.last[:is_bt]; s }
262
+ # Aside from the primary key and the metadata columns created_at, updated_at, and deleted_at, if this table only has
263
+ # foreign keys then it can act as an associative table and thus be used with has_many :through.
264
+ if fks.length > 1 && (tbl_cols - fks.keys - (::Brick.config.metadata_columns || []) - (tbl[:pkey].values.first || [])).length.zero?
265
+ fks.each { |fk| tbl[:hmt_fks][fk.first] = fk.last }
266
+ end
267
+ end
268
+ end
269
+
247
270
 
248
271
  # Returns Brick's `::Gem::Version`, convenient for comparisons. This is
249
272
  # recommended over `::Brick::VERSION::STRING`.
@@ -18,7 +18,7 @@ module Brick
18
18
  desc 'Generates an initializer file for configuring Brick'
19
19
 
20
20
  def create_initializer_file
21
- unless File.exists?(filename = 'config/initializers/brick.rb')
21
+ unless File.exist?(filename = 'config/initializers/brick.rb')
22
22
  # See if we can make suggestions for additional_references
23
23
  resembles_fks = []
24
24
  possible_additional_references = (relations = ::Brick.relations).each_with_object([]) do |v, s|
@@ -47,7 +47,7 @@ module Brick
47
47
  if (relations.fetch(f_table = col_down, nil) ||
48
48
  relations.fetch(f_table = ActiveSupport::Inflector.pluralize(col_down), nil)) &&
49
49
  # Looks pretty promising ... just make sure a model file isn't present
50
- !File.exists?("app/models/#{ActiveSupport::Inflector.singularize(v.first)}.rb")
50
+ !File.exist?("app/models/#{ActiveSupport::Inflector.singularize(v.first)}.rb")
51
51
  s << "['#{v.first}', '#{col}', '#{f_table}']"
52
52
  else
53
53
  resembles_fks << "#{v.first}.#{col}"
@@ -114,7 +114,7 @@ module Brick
114
114
  # # Skip creating a has_many association for these
115
115
  # # (Uses the same exact three-part format as would define an additional_reference)
116
116
  # # Say for instance that we didn't care to display the favourite colours that users have:
117
- # Brick.skip_hms = [['users', 'favourite_colour_id', 'colours']]
117
+ # Brick.exclude_hms = [['users', 'favourite_colour_id', 'colours']]
118
118
 
119
119
  # # By default primary tables involved in a foreign key relationship will indicate a \"has_many\" relationship pointing
120
120
  # # back to the foreign table. In order to represent a \"has_one\" association instead, an override can be provided
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.16
4
+ version: 1.0.19
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-03-26 00:00:00.000000000 Z
11
+ date: 2022-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord