brick 1.0.71 → 1.0.73

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.
@@ -87,9 +87,9 @@ module Brick
87
87
 
88
88
  def path_keys(hm_assoc, fk_name, obj_name, pk)
89
89
  keys = if fk_name.is_a?(Array) && pk.is_a?(Array) # Composite keys?
90
- fk_name.zip(pk.map { |pk_part| "#{obj_name}.#{pk_part}" })
90
+ fk_name.zip(pk.map { |fk_part| "#{obj_name}.#{fk_part}" })
91
91
  else
92
- pk = pk.each_with_object([]) { |pk_part, s| s << "#{obj_name}.#{pk_part}" }
92
+ pk = pk.map { |pk_part| "#{obj_name}.#{pk_part}" }
93
93
  [[fk_name, pk.length == 1 ? pk.first : pk.inspect]]
94
94
  end
95
95
  keys << [hm_assoc.inverse_of.foreign_type, hm_assoc.active_record.name] if hm_assoc.options.key?(:as)
@@ -106,8 +106,8 @@ module Brick
106
106
  return _brick_find_template(*args, **options)
107
107
  end
108
108
 
109
- if @_brick_model
110
- pk = @_brick_model._brick_primary_key(::Brick.relations.fetch(model_name, nil))
109
+ if @_brick_model
110
+ pk = @_brick_model._brick_primary_key(::Brick.relations.fetch(@_brick_model&.table_name, nil))
111
111
  obj_name = model_name.split('::').last.underscore
112
112
  path_obj_name = model_name.underscore.tr('/', '_')
113
113
  table_name = obj_name.pluralize
@@ -159,7 +159,7 @@ module Brick
159
159
  hms_columns << hm_entry
160
160
  when 'show', 'new', 'update'
161
161
  hm_stuff << if hm_fk_name
162
- "<%= link_to '#{assoc_name}', #{hm_assoc.klass.name.underscore.tr('/', '_').pluralize}_path({ #{path_keys(hm_assoc, hm_fk_name, "@#{obj_name}", pk)} }) %>\n"
162
+ "<%= link_to '#{assoc_name}', #{hm_assoc.klass._brick_index}_path({ #{path_keys(hm_assoc, hm_fk_name, "@#{obj_name}", pk)} }) %>\n"
163
163
  else # %%% Would be able to remove this when multiple foreign keys to same destination becomes bulletproof
164
164
  assoc_name
165
165
  end
@@ -173,12 +173,13 @@ module Brick
173
173
  # environment or whatever, then get either the controllers or routes list instead
174
174
  apartment_default_schema = ::Brick.apartment_multitenant && Apartment.default_schema
175
175
  table_options = (::Brick.relations.keys - ::Brick.config.exclude_tables).each_with_object({}) do |tbl, s|
176
+ binding.pry if tbl.is_a?(Symbol)
176
177
  if (tbl_parts = tbl.split('.')).first == apartment_default_schema
177
178
  tbl = tbl_parts.last
178
179
  end
179
180
  s[tbl] = nil
180
181
  end.keys.sort.each_with_object(+'') do |v, s|
181
- s << "<option value=\"#{v.underscore.gsub('.', '/').pluralize}\">#{v}</option>"
182
+ s << "<option value=\"#{v.underscore.gsub('.', '/')}\">#{v}</option>"
182
183
  end.html_safe
183
184
  table_options << '<option value="brick_status">(Status)</option>'.html_safe if ::Brick.config.add_status
184
185
  table_options << '<option value="brick_orphans">(Orphans)</option>'.html_safe if is_orphans
@@ -351,15 +352,39 @@ def hide_bcrypt(val, max_len = 200)
351
352
  end
352
353
  def display_value(col_type, val)
353
354
  case col_type
354
- when 'geometry'
355
+ when 'geometry', 'geography'
355
356
  if Object.const_defined?('RGeo')
356
357
  @is_mysql = ActiveRecord::Base.connection.adapter_name == 'Mysql2' if @is_mysql.nil?
357
- if @is_mysql
358
- # MySQL's \"Internal Geometry Format\" is like WKB, but with an initial 4 bytes that indicates the SRID.
359
- srid = val[0..3].unpack('I')
360
- val = val[4..-1]
358
+ @is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer' if @is_mssql.nil?
359
+ val_err = nil
360
+ if @is_mysql || @is_mssql
361
+ # MySQL's \"Internal Geometry Format\" and MSSQL's Geography are like WKB, but with an initial 4 bytes that indicates the SRID.
362
+ if (srid = val&.[](0..3)&.unpack('I'))
363
+ val = val.force_encoding('BINARY')[4..-1].bytes
364
+
365
+ # MSSQL spatial bitwise flags, often 0C for a point:
366
+ # xxxx xxx1 = HasZValues
367
+ # xxxx xx1x = HasMValues
368
+ # xxxx x1xx = IsValid
369
+ # xxxx 1xxx = IsSinglePoint
370
+ # xxx1 xxxx = IsSingleLineSegment
371
+ # xx1x xxxx = IsWholeGlobe
372
+ # Convert Microsoft's unique geography binary to standard WKB
373
+ # (MSSQL point usually has two doubles, lng / lat, and can also have Z)
374
+ if @is_mssql
375
+ if val[0] == 1 && (val[1] & 8 > 0) && # Single point?
376
+ (val.length - 2) % 8 == 0 && val.length < 27 # And containing up to three 8-byte values?
377
+ idx = 2
378
+ new_val = [0, 0, 0, 0, 1]
379
+ new_val.concat(val[idx - 8...idx].reverse) while (idx += 8) <= val.length
380
+ val = new_val
381
+ else
382
+ val_err = '(Microsoft internal SQL geography type)'
383
+ end
384
+ end
385
+ end
361
386
  end
362
- RGeo::WKRep::WKBParser.new.parse(val)
387
+ val_err || (val ? RGeo::WKRep::WKBParser.new.parse(val.pack('c*')) : nil)
363
388
  else
364
389
  '(Add RGeo gem to parse geometry detail)'
365
390
  end
@@ -448,7 +473,7 @@ function changeout(href, param, value, trimAfter) {
448
473
  var params = hrefParts.length > 1 ? hrefParts[1].split(\"&\") : [];
449
474
  if (param === undefined || param === null || param === -1) {
450
475
  hrefParts = hrefParts[0].split(\"://\");
451
- var pathParts = hrefParts[hrefParts.length - 1].split(\"/\");
476
+ var pathParts = hrefParts[hrefParts.length - 1].split(\"/\").filter(function (pp) {return pp !== \"\";});
452
477
  if (value === undefined)
453
478
  // A couple possibilities if it's namespaced, starting with two parts in the path -- and then try just one
454
479
  return [pathParts.slice(1, 3).join('/'), pathParts.slice(1, 2)[0]];
@@ -562,7 +587,7 @@ erDiagram
562
587
  <%= \"#\{model_short_name} }o..o{ #\{hm_name} : \\\"#\{hm.first}\\\"\".html_safe %><%
563
588
  else # has_many
564
589
  %> <%= \"#\{model_short_name} ||--o{ #\{hm_name} : \\\"#\{
565
- hm_name unless hm.first.to_s == hm_class.name.underscore.pluralize.tr('/', '_')
590
+ hm.first.to_s unless hm.first.to_s.downcase == hm_class.name.underscore.pluralize.tr('/', '_')
566
591
  }\\\"\".html_safe %><%
567
592
  end %>
568
593
  <% end
@@ -604,7 +629,7 @@ erDiagram
604
629
  end
605
630
  if Object.const_defined?('DutyFree')
606
631
  template_link = "
607
- <%= link_to 'CSV', #{table_name}_path(format: :csv) %> &nbsp; <a href=\"#\" id=\"sheetsLink\">Sheets</a>
632
+ <%= link_to 'CSV', #{@_brick_model._brick_index}_path(format: :csv) %> &nbsp; <a href=\"#\" id=\"sheetsLink\">Sheets</a>
608
633
  <div id=\"dropper\" contenteditable=\"true\"></div>
609
634
  <input type=\"button\" id=\"btnImport\" value=\"Import\">
610
635
 
@@ -667,7 +692,7 @@ erDiagram
667
692
  console.log(\"x1\", sheetUrl);
668
693
 
669
694
  // Get JSON data
670
- fetch(changeout(<%= #{table_name}_path(format: :js).inspect.html_safe %>, \"_brick_schema\", brickSchema)).then(function (response) {
695
+ fetch(changeout(<%= #{@_brick_model._brick_index}_path(format: :js).inspect.html_safe %>, \"_brick_schema\", brickSchema)).then(function (response) {
671
696
  response.json().then(function (data) {
672
697
  gapi.client.sheets.spreadsheets.values.append({
673
698
  spreadsheetId: spreadsheetId,
@@ -698,7 +723,7 @@ erDiagram
698
723
  <select id=\"schema\">#{schema_options}</select>" if ::Brick.config.schema_behavior[:multitenant] && ::Brick.db_schemas.length > 1}
699
724
  <select id=\"tbl\">#{table_options}</select>
700
725
  <table id=\"resourceName\"><tr>
701
- <td><h1>#{model_plural = model_name.pluralize}</h1></td>
726
+ <td><h1>#{model_name}</h1></td>
702
727
  <td id=\"imgErd\" title=\"Show ERD\"></td>
703
728
  </tr></table>#{template_link}<%
704
729
  if (description = (relation = Brick.relations[#{model_name}.table_name])&.fetch(:description, nil)) %><%=
@@ -712,10 +737,10 @@ erDiagram
712
737
  origin = (key_parts = k.split('.')).length == 1 ? #{model_name} : #{model_name}.reflect_on_association(key_parts.first).klass
713
738
  if (destination_fk = Brick.relations[origin.table_name][:fks].values.find { |fk| fk[:fk] == key_parts.last }) &&
714
739
  (obj = (destination = origin.reflect_on_association(destination_fk[:assoc_name])&.klass)&.find(id)) %>
715
- <h3>for <%= link_to \"#{"#\{obj.brick_descrip\} (#\{destination.name\})\""}, send(\"#\{destination.name.underscore.tr('/', '_')\}_path\".to_sym, id) %></h3><%
740
+ <h3>for <%= link_to \"#{"#\{obj.brick_descrip\} (#\{destination.name\})\""}, send(\"#\{destination._brick_index\}_path\".to_sym, id) %></h3><%
716
741
  end
717
742
  end %>
718
- (<%= link_to 'See all #{model_plural.split('::').last}', #{path_obj_name.pluralize}_path %>)
743
+ (<%= link_to 'See all #{model_name.split('::').last.pluralize}', #{@_brick_model._brick_index}_path %>)
719
744
  <% end
720
745
  # COLUMN EXCLUSIONS
721
746
  if @_brick_excl&.present? %>
@@ -768,7 +793,7 @@ erDiagram
768
793
  end
769
794
  elsif col # HM column
770
795
  s << \"<th#\{' x-order=\"' + col_name + '\"' if true}>#\{col[2]} \"
771
- s << (col.first ? \"#\{col[3]}\" : \"#\{link_to(col[3], send(\"#\{col[1].name.underscore.tr('/', '_').pluralize}_path\"))}\")
796
+ s << (col.first ? \"#\{col[3]}\" : \"#\{link_to(col[3], send(\"#\{col[1]._brick_index}_path\"))}\")
772
797
  else # Bad column name!
773
798
  s << \"<th title=\\\"<< Unknown column >>\\\">#\{col_name}\"
774
799
  end
@@ -776,7 +801,11 @@ erDiagram
776
801
  end.html_safe
777
802
  %></tr></thead>
778
803
  <tbody>
779
- <% @#{table_name}.each do |#{obj_name}|
804
+ <% # %%% Have once gotten this error with MSSQL referring to http://localhost:3000/warehouse/cold_room_temperatures__archive
805
+ # ActiveRecord::StatementTimeout in Warehouse::ColdRoomTemperatures_Archive#index
806
+ # TinyTds::Error: Adaptive Server connection timed out
807
+ # (After restarting the server it worked fine again.)
808
+ @#{table_name}.each do |#{obj_name}|
780
809
  hms_cols = {#{hms_columns.join(', ')}} %>
781
810
  <tr>#{"
782
811
  <td><%= link_to '⇛', #{path_obj_name}_path(#{obj_pk}), { class: 'big-arrow' } %></td>" if obj_pk}
@@ -811,10 +840,11 @@ erDiagram
811
840
  else
812
841
  \"#\{hms_col[1] || 'View'\} #\{hms_col.first}\"
813
842
  end %>
814
- <%= link_to txt, send(\"#\{klass.name.underscore.tr('/', '_').pluralize}_path\".to_sym, hms_col[2]) unless hms_col[1]&.zero? %>
843
+ <%= link_to txt, send(\"#\{klass._brick_index}_path\".to_sym, hms_col[2]) unless hms_col[1]&.zero? %>
815
844
  <% end
816
845
  elsif (col = cols[col_name])
817
- %><%= display_value(col&.type || col&.sql_type, val) %><%
846
+ col_type = col&.sql_type == 'geography' ? col.sql_type : col&.type
847
+ %><%= display_value(col_type || col&.sql_type, val) %><%
818
848
  else # Bad column name!
819
849
  %>?<%
820
850
  end
@@ -908,7 +938,7 @@ erDiagram
908
938
  if (description = (relation = Brick.relations[#{model_name}.table_name])&.fetch(:description, nil)) %><%=
909
939
  description %><br><%
910
940
  end
911
- %><%= link_to '(See all #{obj_name.pluralize})', #{path_obj_name.pluralize}_path %>
941
+ %><%= link_to '(See all #{obj_name.pluralize})', #{@_brick_model._brick_index}_path %>
912
942
  #{erd_markup}
913
943
  <% if obj %>
914
944
  <br><br>
@@ -974,7 +1004,8 @@ end
974
1004
  \"<span class=\\\"orphan\\\">Orphaned ID: #\{val}</span>\".html_safe
975
1005
  end %>
976
1006
  <% else
977
- case (col_type = col.type || col.sql_type)
1007
+ col_type = col.sql_type == 'geography' ? col.sql_type : col.type
1008
+ case (col_type ||= col.sql_type)
978
1009
  when :string, :text %>
979
1010
  <% if is_bcrypt?(val) # || .readonly?
980
1011
  is_revert = false %>
@@ -1008,8 +1039,8 @@ end
1008
1039
  <% when :binary, :primary_key
1009
1040
  is_revert = false %>
1010
1041
  <% else %>
1011
- <%= display_value(col_type, val)
1012
- is_revert = false %>
1042
+ <%= is_revert = false
1043
+ display_value(col_type, val) %>
1013
1044
  <% end
1014
1045
  end
1015
1046
  if is_revert
@@ -1077,7 +1108,7 @@ flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
1077
1108
  var imgErd = document.getElementById(\"imgErd\");
1078
1109
  var mermaidErd = document.getElementById(\"mermaidErd\");
1079
1110
  var mermaidCode;
1080
- var cbs = {<%= callbacks.map { |k, v| \"#\{k}: \\\"#\{v.name.underscore.pluralize}\\\"\" }.join(', ').html_safe %>};
1111
+ var cbs = {<%= callbacks.map { |k, v| \"#\{k}: \\\"#\{send(\"#\{v._brick_index}_path\".to_sym)}\\\"\" }.join(', ').html_safe %>};
1081
1112
  if (imgErd) imgErd.addEventListener(\"click\", showErd);
1082
1113
  function showErd() {
1083
1114
  imgErd.style.display = \"none\";
@@ -1101,7 +1132,7 @@ flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
1101
1132
  function (evt) {
1102
1133
  location.href = changeout(changeout(
1103
1134
  changeout(location.href, '_brick_order', null), // Remove any ordering
1104
- -1, cbs[this.id]), \"_brick_erd\", \"1\");
1135
+ -1, cbs[this.id].replace(/^[\/]+/, \"\")), \"_brick_erd\", \"1\");
1105
1136
  }
1106
1137
  );
1107
1138
  }
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 71
8
+ TINY = 73
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
@@ -125,8 +125,8 @@ module Brick
125
125
  class << self
126
126
  attr_accessor :default_schema, :db_schemas, :routes_done, :is_oracle
127
127
 
128
- def set_db_schema(params)
129
- schema = params['_brick_schema'] || 'public'
128
+ def set_db_schema(params = nil)
129
+ schema = (params ? params['_brick_schema'] : ::Brick.default_schema) || 'public'
130
130
  if schema && ::Brick.db_schemas&.include?(schema)
131
131
  ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
132
132
  schema
@@ -152,16 +152,19 @@ module Brick
152
152
  end
153
153
 
154
154
  # Convert spaces to underscores if the second character and onwards is mixed case
155
- def namify(name)
155
+ def namify(name, action = nil)
156
+ has_uppers = name =~ /[A-Z]+/
157
+ has_lowers = name =~ /[a-z]+/
158
+ name.downcase! if has_uppers && action == :downcase
156
159
  if name.include?(' ')
157
160
  # All uppers or all lowers?
158
- if name[1..-1] =~ /^[A-Z0-9_]+$/ || name[1..-1] =~ /^[a-z0-9_]+$/
161
+ if !has_uppers || !has_lowers
159
162
  name.titleize.tr(' ', '_')
160
163
  else # Mixed uppers and lowers -- just remove existing spaces
161
164
  name.tr(' ', '')
162
165
  end
163
166
  else
164
- name
167
+ action == :underscore ? name.underscore : name
165
168
  end
166
169
  end
167
170
 
@@ -265,6 +268,26 @@ module Brick
265
268
  !!Brick.config.enable_routes
266
269
  end
267
270
 
271
+ # @api public
272
+ def enable_api=(path)
273
+ Brick.config.enable_api = path
274
+ end
275
+
276
+ # @api public
277
+ def enable_api
278
+ Brick.config.enable_api
279
+ end
280
+
281
+ # @api public
282
+ def api_root=(path)
283
+ Brick.config.api_root = path
284
+ end
285
+
286
+ # @api public
287
+ def api_root
288
+ Brick.config.api_root
289
+ end
290
+
268
291
  # @api public
269
292
  def skip_database_views=(value)
270
293
  Brick.config.skip_database_views = value
@@ -483,7 +506,7 @@ In config/initializers/brick.rb appropriate entries would look something like:
483
506
  # %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
484
507
  # If auto-controllers and auto-models are both enabled then this makes sense:
485
508
  ::Brick.relations.each do |rel_name, v|
486
- rel_name = rel_name.split('.').map { |x| ::Brick.namify(x).underscore }
509
+ rel_name = rel_name.split('.').map { |rel_part| ::Brick.namify(rel_part, :underscore) }
487
510
  schema_names = rel_name[0..-2]
488
511
  schema_names.shift if ::Brick.apartment_multitenant && schema_names.first == Apartment.default_schema
489
512
  # %%% If more than one schema has the same table name, will need to add a schema name prefix to have uniqueness
@@ -493,10 +516,12 @@ In config/initializers/brick.rb appropriate entries would look something like:
493
516
  options[:only] = [:index, :show] if v.key?(:isView)
494
517
  if schema_names.present? # && !Object.const_defined('Apartment')
495
518
  send(:namespace, schema_names.first) do
496
- send(:resources, controller_name.to_sym, **options)
519
+ send(:resources, k.to_sym, **options)
497
520
  end
521
+ send(:get, "/api/v1/#{schema_names.first}/#{k}", { to: "#{schema_names.first}/#{controller_name}#index" }) if Object.const_defined?('Rswag::Ui')
498
522
  else
499
- send(:resources, controller_name.to_sym, **options)
523
+ send(:resources, k.to_sym, **options)
524
+ send(:get, "/api/v1/#{k}", { to: "#{controller_name}#index" }) if Object.const_defined?('Rswag::Ui')
500
525
  end
501
526
  end
502
527
  if ::Brick.config.add_status && instance_variable_get(:@set).named_routes.names.exclude?(:brick_status)
@@ -506,7 +531,11 @@ In config/initializers/brick.rb appropriate entries would look something like:
506
531
  get('/brick_orphans', to: 'brick_gem#orphans', as: 'brick_orphans')
507
532
  end
508
533
  end
509
- send(:get, '/api-docs/v1/swagger.json', { to: 'brick_swagger#index' }) if Object.const_defined?('Rswag::Ui')
534
+ if Object.const_defined?('Rswag::Ui') && doc_endpoint = Rswag::Ui.config.config_object[:urls].last
535
+ # Serves JSON swagger info from a path such as '/api-docs/v1/swagger.json'
536
+ puts "Mounting swagger info endpoint for \"#{doc_endpoint[:name]}\" on #{doc_endpoint[:url]}"
537
+ send(:get, doc_endpoint[:url], { to: 'brick_swagger#index' })
538
+ end
510
539
  end
511
540
  super
512
541
  end
@@ -726,6 +755,63 @@ ActiveSupport.on_load(:active_record) do
726
755
  end
727
756
  end
728
757
  end
758
+
759
+ # Migration stuff
760
+ module ConnectionAdapters
761
+ # Override the downcasing implementation from the OracleEnhanced gem as it has bad regex
762
+ if const_defined?(:OracleEnhanced)
763
+ module OracleEnhanced::Quoting
764
+ private
765
+
766
+ def oracle_downcase(column_name)
767
+ return nil if column_name.nil?
768
+
769
+ /^[A-Za-z0-9_]+$/ =~ column_name ? column_name.downcase : column_name
770
+ end
771
+ end
772
+ end
773
+ if const_defined?(:SQLServerAdapter)
774
+ class SQLServer::TableDefinition
775
+ alias _brick_new_column_definition new_column_definition
776
+ def new_column_definition(name, type, **options)
777
+ case type
778
+ when :serial
779
+ type = :integer
780
+ options[:is_identity] = true
781
+ when :bigserial
782
+ type = :bigint
783
+ options[:is_identity] = true
784
+ end
785
+ _brick_new_column_definition(name, type, **options)
786
+ end
787
+ def serial(*args)
788
+ options = args.extract_options!
789
+ options[:is_identity] = true
790
+ args.each { |name| column(name, 'integer', options) }
791
+ end
792
+ def bigserial(*args)
793
+ options = args.extract_options!
794
+ options[:is_identity] = true
795
+ args.each { |name| column(name, 'bigint', options) }
796
+ end
797
+ # Seems that geography gets used a fair bit in MSSQL
798
+ def geography(*args)
799
+ options = args.extract_options!
800
+ # options[:precision] ||= 8
801
+ # options[:scale] ||= 2
802
+ args.each { |name| column(name, 'geography', options) }
803
+ end
804
+ end
805
+ class SQLServerAdapter
806
+ unless respond_to?(:schema_exists?)
807
+ def schema_exists?(schema)
808
+ schema_sql = 'SELECT 1 FROM sys.schemas WHERE name = ?'
809
+ ActiveRecord::Base.execute_sql(schema_sql, schema).present?
810
+ end
811
+ end
812
+ end
813
+ end
814
+ end
729
815
  end
730
816
  # rubocop:enable Lint/ConstantDefinitionInBlock
731
817
 
@@ -147,6 +147,8 @@ module Brick
147
147
  # Brick.enable_controllers = true # Setting this to \"false\" will disable controllers in development
148
148
  # Brick.enable_views = true # Setting this to \"false\" will disable views in development
149
149
 
150
+ # Brick.api_root = '/api/v1/' # Path from which to serve out API resources when the RSwag gem is present
151
+
150
152
  # # By default models are auto-created for database views, and set to be read-only. This can be skipped.
151
153
  # Brick.skip_database_views = true
152
154
 
@@ -23,6 +23,21 @@ module Brick
23
23
  'time with time zone' => 'time',
24
24
  'double precision' => 'float',
25
25
  'smallint' => 'integer', # %%% Need to put in "limit: 2"
26
+ # # Oracle data types
27
+ 'VARCHAR2' => 'string',
28
+ 'CHAR' => 'string',
29
+ ['NUMBER', 22] => 'integer',
30
+ /^INTERVAL / => 'string', # Time interval stuff like INTERVAL YEAR(2) TO MONTH, INTERVAL '999' DAY(3), etc
31
+ 'XMLTYPE' => 'xml',
32
+ 'RAW' => 'binary',
33
+ 'SDO_GEOMETRY' => 'geometry',
34
+ # MSSQL data types
35
+ 'int' => 'integer',
36
+ 'nvarchar' => 'string',
37
+ 'nchar' => 'string',
38
+ 'datetime2' => 'timestamp',
39
+ 'bit' => 'boolean',
40
+ 'varbinary' => 'binary',
26
41
  # Sqlite data types
27
42
  'TEXT' => 'text',
28
43
  '' => 'string',
@@ -122,7 +137,7 @@ module Brick
122
137
  fringe.each do |tbl|
123
138
  next unless (relation = ::Brick.relations.fetch(tbl, nil))&.fetch(:cols, nil)&.present?
124
139
 
125
- pkey_cols = (rpk = relation[:pkey].values.flatten) & (arpk = [ActiveRecord::Base.primary_key].flatten)
140
+ pkey_cols = (rpk = relation[:pkey].values.flatten) & (arpk = [ApplicationRecord.primary_key].flatten.sort)
126
141
  # In case things aren't as standard
127
142
  if pkey_cols.empty?
128
143
  pkey_cols = if rpk.empty? && relation[:cols][arpk.first]&.first == key_type
@@ -141,7 +156,7 @@ module Brick
141
156
  end
142
157
  unless schema.blank? || built_schemas.key?(schema)
143
158
  mig = +" def change\n create_schema(:#{schema}) unless schema_exists?(:#{schema})\n end\n"
144
- migration_file_write(mig_path, "create_db_schema_#{schema}", current_mig_time += 1.minute, ar_version, mig)
159
+ migration_file_write(mig_path, "create_db_schema_#{schema.underscore}", current_mig_time += 1.minute, ar_version, mig)
145
160
  built_schemas[schema] = nil
146
161
  end
147
162
 
@@ -151,11 +166,16 @@ module Brick
151
166
  # a column definition which includes :primary_key -- %%% also using a data type of bigserial or serial
152
167
  # if this one has come in as bigint or integer.
153
168
  pk_is_also_fk = fkey_cols.any? { |assoc| pkey_cols&.first == assoc[:fk] } ? pkey_cols&.first : nil
154
- # Support missing primary key (by adding: ,id: false)
169
+ # Support missing primary key (by adding: , id: false)
155
170
  id_option = if pk_is_also_fk || !pkey_cols&.present?
171
+ needs_serial_col = true
156
172
  ', id: false'
157
- elsif ((pkey_col_first = relation[:cols][pkey_cols&.first]&.first) &&
158
- (pkey_col_first = SQL_TYPES[pkey_col_first] || pkey_col_first) != key_type)
173
+ elsif ((pkey_col_first = (col_def = relation[:cols][pkey_cols&.first])&.first) &&
174
+ (pkey_col_first = SQL_TYPES[pkey_col_first] || SQL_TYPES[col_def&.[](0..1)] ||
175
+ SQL_TYPES.find { |r| r.first.is_a?(Regexp) && pkey_col_first =~ r.first }&.last ||
176
+ pkey_col_first
177
+ ) != key_type
178
+ )
159
179
  case pkey_col_first
160
180
  when 'integer'
161
181
  ', id: :serial'
@@ -164,9 +184,14 @@ module Brick
164
184
  else
165
185
  ", id: :#{pkey_col_first}" # Something like: id: :integer, primary_key: :businessentityid
166
186
  end +
167
- (pkey_cols.first ? ", primary_key: :#{pkey_cols.first}" : '') +
168
- (!is_4x_rails && (comment = relation&.fetch(:description, nil))&.present? ? ", comment: #{comment.inspect}" : '')
187
+ (pkey_cols.first ? ", primary_key: :#{pkey_cols.first}" : '')
169
188
  end
189
+ if !id_option && pkey_cols.sort != arpk
190
+ id_option = ", primary_key: :#{pkey_cols.first}"
191
+ end
192
+ if !is_4x_rails && (comment = relation&.fetch(:description, nil))&.present?
193
+ (id_option ||= +'') << ", comment: #{comment.inspect}"
194
+ end
170
195
  # Find the ActiveRecord class in order to see if the columns have comments
171
196
  unless is_4x_rails
172
197
  klass = begin
@@ -187,18 +212,24 @@ module Brick
187
212
  possible_ts = [] # Track possible generic timestamps
188
213
  add_fks = [] # Track foreign keys to add after table creation
189
214
  relation[:cols].each do |col, col_type|
190
- sql_type = SQL_TYPES[col_type.first] || col_type.first
191
- suffix = col_type[3] ? +', null: false' : +''
215
+ sql_type = SQL_TYPES[col_type.first] || SQL_TYPES[col_type[0..1]] ||
216
+ SQL_TYPES.find { |r| r.first.is_a?(Regexp) && col_type.first =~ r.first }&.last ||
217
+ col_type.first
218
+ suffix = col_type[3] || pkey_cols&.include?(col) ? +', null: false' : +''
192
219
  if !is_4x_rails && klass && (comment = klass.columns_hash.fetch(col, nil)&.comment)&.present?
193
220
  suffix << ", comment: #{comment.inspect}"
194
221
  end
195
222
  # Determine if this column is used as part of a foreign key
196
- if fk = fkey_cols.find { |assoc| col == assoc[:fk] }
223
+ if (fk = fkey_cols.find { |assoc| col == assoc[:fk] })
197
224
  to_table = fk[:inverse_table].split('.')
198
225
  to_table = to_table.length == 1 ? ":#{to_table.first}" : "'#{fk[:inverse_table]}'"
226
+ if needs_serial_col && pkey_cols&.include?(col) && (new_serial_type = {'integer' => 'serial', 'bigint' => 'bigserial'}[sql_type])
227
+ sql_type = new_serial_type
228
+ needs_serial_col = false
229
+ end
199
230
  if fk[:fk] != "#{fk[:assoc_name].singularize}_id" # Need to do our own foreign_key tricks, not use references?
200
231
  column = fk[:fk]
201
- mig << " t.#{sql_type} :#{column}#{suffix}\n"
232
+ mig << emit_column(sql_type, column, suffix)
202
233
  add_fks << [to_table, column, ::Brick.relations[fk[:inverse_table]]]
203
234
  else
204
235
  suffix << ", type: :#{sql_type}" unless sql_type == key_type
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.71
4
+ version: 1.0.73
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-09-16 00:00:00.000000000 Z
11
+ date: 2022-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord