brick 1.0.105 → 1.0.107

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: 48fc69654d7cbdf5c7feb9ba7803b7a953c6c0ccd3c176614fe9a61994bb39e1
4
- data.tar.gz: f3a4837d334cf97cb491cacd658cb6e6d8e3f3a5c640f80c151a083bf6811b7a
3
+ metadata.gz: 74bd7cd6539a5f048aecc3e02be8912e1bc77851ac8b9f65402892e964a024de
4
+ data.tar.gz: c32c53d0012f2a9292463d803d0c679d4f32800c8931c3361469aa22c09a3fc5
5
5
  SHA512:
6
- metadata.gz: e51134710bae78f033b9dcf7827cd7c087cfd642ef8bc6244e2429f22158a80e9f804022bc703f55fa55f85cbc009ff25440289205ade1b415f5ba0af3d44c77
7
- data.tar.gz: d6069676ad70e8401f7597b92b641f0f95e3c28fffa54747f630932500c778d7b4ac967c5bbd70059647d11d398598362d9c0bb5c82ef0500b1025371104a578
6
+ metadata.gz: f84ee2aae03622a4ca67a6921daf7e975f85601e66fc608c2357613da976414d2ceeec87276887232a832413116ec1495638c883ecc712db3706f51c34efbd44
7
+ data.tar.gz: e97fbe41eca4d839795f7567bfd3e8a3d6494d4887032fa5f0f87d88ba3c9fdc810bde8a259c3cbf0b8f944c8b345c33290aa5d1e8f7aa67737e7fe7be248a6d
data/lib/brick/config.rb CHANGED
@@ -95,20 +95,19 @@ module Brick
95
95
  end
96
96
 
97
97
  def api_roots
98
- ver = api_version
99
- @mutex.synchronize { @api_roots || ["/api/#{ver}/"] }
98
+ @mutex.synchronize { @api_roots || ["/api/v1/"] }
100
99
  end
101
100
 
102
101
  def api_roots=(path)
103
102
  @mutex.synchronize { @api_roots = path }
104
103
  end
105
104
 
106
- def api_version
107
- @mutex.synchronize { @api_version || 'v1' }
105
+ def api_filter
106
+ @mutex.synchronize { @api_filter }
108
107
  end
109
108
 
110
- def api_version=(ver)
111
- @mutex.synchronize { @api_version = ver }
109
+ def api_filter=(proc)
110
+ @mutex.synchronize { @api_filter = proc }
112
111
  end
113
112
 
114
113
  # Additional table associations to use (Think of these as virtual foreign keys perhaps)
@@ -1526,9 +1526,10 @@ class Object
1526
1526
  self.protect_from_forgery unless: -> { self.request.format.js? }
1527
1527
  unless is_avo
1528
1528
  self.define_method :index do
1529
+ request_ver = request.path.split('/')[-2]
1529
1530
  current_api_root = ::Brick.config.api_roots.find do |ar|
1530
1531
  request.path.start_with?(ar) || # Exact match?
1531
- request.path.split('/')[-2] == ar.split('/').last # Version at least matches?
1532
+ request_ver == ar.split('/').last # Version at least matches?
1532
1533
  end
1533
1534
  if (current_api_root || is_openapi) &&
1534
1535
  !params&.key?('_brick_schema') &&
@@ -1542,51 +1543,72 @@ class Object
1542
1543
  _schema, @_is_show_schema_list = ::Brick.set_db_schema(params || api_params)
1543
1544
 
1544
1545
  if is_openapi
1546
+ api_name = Rswag::Ui.config.config_object[:urls].find do |api_url|
1547
+ api_url[:url] == request.path
1548
+ end&.fetch(:name, 'API documentation')
1545
1549
  current_api_ver = current_api_root.split('/').last&.[](1..-1).to_i
1546
- json = { 'openapi': '3.0.1', 'info': { 'title': Rswag::Ui.config.config_object[:urls].last&.fetch(:name, 'API documentation'), 'version': ::Brick.config.api_version },
1550
+ json = { 'openapi': '3.0.1', 'info': { 'title': api_name, 'version': request_ver },
1547
1551
  'servers': [
1548
- { 'url': '{scheme}://{defaultHost}', 'variables': {
1549
- 'scheme': { 'default': request.env['rack.url_scheme'] },
1550
- 'defaultHost': { 'default': request.env['HTTP_HOST'] }
1551
- } }
1552
+ { 'url': '{scheme}://{defaultHost}',
1553
+ 'variables': {
1554
+ 'scheme': { 'default': request.env['rack.url_scheme'] },
1555
+ 'defaultHost': { 'default': request.env['HTTP_HOST'] }
1556
+ }
1557
+ }
1552
1558
  ]
1553
1559
  }
1554
- json['paths'] = relations.each_with_object({}) do |relation, s|
1555
- unless ::Brick.config.enable_api == false
1560
+ unless ::Brick.config.enable_api == false
1561
+ json['paths'] = relations.each_with_object({}) do |relation, s|
1556
1562
  next if (api_vers = relation.last.fetch(:api, nil)) &&
1557
- !(api_ver_path = api_vers[current_api_ver])
1558
-
1559
- relation_name = api_ver_path || relation.first.tr('.', '/')
1560
- table_description = relation.last[:description]
1561
- s["#{current_api_root}#{relation_name}"] = {
1562
- 'get': {
1563
- 'summary': "list #{relation.first}",
1564
- 'description': table_description,
1565
- 'parameters': relation.last[:cols].map do |k, v|
1566
- param = { in: 'query', 'name' => k, 'schema': { 'type': v.first } }
1567
- if (col_descrip = relation.last.fetch(:col_descrips, nil)&.fetch(k, nil))
1568
- param['description'] = col_descrip
1569
- end
1570
- param
1571
- end,
1572
- 'responses': { '200': { 'description': 'successful' } }
1573
- }
1574
- }
1575
-
1576
- s["#{current_api_root}#{relation_name}/{id}"] = {
1577
- 'patch': {
1578
- 'summary': "update a #{relation.first.singularize}",
1579
- 'description': table_description,
1580
- 'parameters': relation.last[:cols].reject { |k, v| Brick.config.metadata_columns.include?(k) }.map do |k, v|
1581
- param = { 'name' => k, 'schema': { 'type': v.first } }
1582
- if (col_descrip = relation.last.fetch(:col_descrips, nil)&.fetch(k, nil))
1583
- param['description'] = col_descrip
1563
+ !(api_ver_paths = api_vers[current_api_ver] || api_vers[nil])
1564
+
1565
+ schema_tag = {}
1566
+ if (schema_name = relation.last&.fetch(:schema, nil))
1567
+ schema_tag['tags'] = [schema_name]
1568
+ end
1569
+ all_actions = relation.last.key?(:isView) ? [:index, :show] : ::Brick::ALL_API_ACTIONS
1570
+ (api_ver_paths || { relation.first => all_actions }).each do |api_ver_path, actions|
1571
+ relation_name = (api_ver_path || relation.first).tr('.', '/')
1572
+ table_description = relation.last[:description]
1573
+ { :index => [:get, 'list'], :create => [:post, 'create a'] }.each do |k, v|
1574
+ unless actions&.exclude?(k)
1575
+ this_resource = (s["#{current_api_root}#{relation_name}"] ||= {})
1576
+ this_resource[v.first] = {
1577
+ 'summary': "#{v[1]} #{relation.first}",
1578
+ 'description': table_description,
1579
+ 'parameters': relation.last[:cols].map do |k2, v2|
1580
+ param = { in: 'query', 'name': k2, 'schema': { 'type': v2.first } }
1581
+ if (col_descrip = relation.last.fetch(:col_descrips, nil)&.fetch(k2, nil))
1582
+ param['description'] = col_descrip
1583
+ end
1584
+ param
1585
+ end,
1586
+ 'responses': { '200': { 'description': 'successful' } }
1587
+ }.merge(schema_tag)
1588
+ end
1589
+ end
1590
+
1591
+ # We have not yet implemented the #show action
1592
+ if (id_col = relation.last[:pkey]&.values&.first&.first) # ... ID-dependent stuff
1593
+ { :update => [:patch, 'update'], :destroy => [:delete, 'delete'] }.each do |k, v|
1594
+ unless actions&.exclude?(k)
1595
+ this_resource = (s["#{current_api_root}#{relation_name}/{#{id_col}}"] ||= {})
1596
+ this_resource[v.first] = {
1597
+ 'summary': "#{v[1]} a #{relation.first.singularize}",
1598
+ 'description': table_description,
1599
+ 'parameters': relation.last[:cols].reject { |k1, _v1| Brick.config.metadata_columns.include?(k1) }.map do |k2, v2|
1600
+ param = { 'name': k2, 'schema': { 'type': v2.first } }
1601
+ if (col_descrip = relation.last.fetch(:col_descrips, nil)&.fetch(k2, nil))
1602
+ param['description'] = col_descrip
1603
+ end
1604
+ param
1605
+ end,
1606
+ 'responses': { '200': { 'description': 'successful' } }
1607
+ }.merge(schema_tag)
1584
1608
  end
1585
- param
1586
- end,
1587
- 'responses': { '200': { 'description': 'successful' } }
1588
- }
1589
- } unless relation.last.fetch(:isView, nil)
1609
+ end
1610
+ end
1611
+ end # Do multiple api_ver_paths
1590
1612
  end
1591
1613
  end
1592
1614
  render inline: json.to_json, content_type: request.format
@@ -1600,11 +1622,11 @@ class Object
1600
1622
  end
1601
1623
  render inline: exported_csv, content_type: request.format
1602
1624
  return
1603
- elsif request.format == :js || current_api_root # Asking for JSON?
1604
- # %%% Add: where, order, page, page_size, offset, limit
1605
- data = (model.is_view? || !Object.const_defined?('DutyFree')) ? model.limit(1000) : model.df_export(model.brick_import_template)
1606
- render inline: { data: data }.to_json, content_type: request.format == '*/*' ? 'application/json' : request.format
1607
- return
1625
+ # elsif request.format == :js || current_api_root # Asking for JSON?
1626
+ # # %%% Add: where, order, page, page_size, offset, limit
1627
+ # data = (model.is_view? || !Object.const_defined?('DutyFree')) ? model.limit(1000) : model.df_export(model.brick_import_template)
1628
+ # render inline: { data: data }.to_json, content_type: request.format == '*/*' ? 'application/json' : request.format
1629
+ # return
1608
1630
  end
1609
1631
 
1610
1632
  # Normal (not swagger or CSV) request
@@ -1616,8 +1638,15 @@ class Object
1616
1638
 
1617
1639
  ar_relation = ActiveRecord.version < Gem::Version.new('4') ? model.preload : model.all
1618
1640
  @_brick_params = ar_relation.brick_select(params, (selects ||= []), order_by,
1619
- translations = {},
1620
- join_array = ::Brick::JoinArray.new)
1641
+ translations = {},
1642
+ join_array = ::Brick::JoinArray.new)
1643
+
1644
+ if request.format == :js || current_api_root # Asking for JSON?
1645
+ data = ar_relation.respond_to?(:_select!) ? ar_relation.dup._select!(*selects) : ar_relation.select(selects)
1646
+ render inline: { data: data }.to_json, content_type: request.format == '*/*' ? 'application/json' : request.format
1647
+ return
1648
+ end
1649
+
1621
1650
  # %%% Add custom HM count columns
1622
1651
  # %%% What happens when the PK is composite?
1623
1652
  counts = model._br_hm_counts.each_with_object([]) do |v, s|
@@ -2127,7 +2156,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
2127
2156
  # AND kcu2.TABLE_NAME = ?;", Apartment::Tenant.current, table_name
2128
2157
  fk_references = ActiveRecord::Base.execute_sql(sql)
2129
2158
  when 'SQLite'
2130
- sql = "SELECT m.name, fkl.\"from\", fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
2159
+ sql = "SELECT NULL AS constraint_schema, m.name, fkl.\"from\", NULL AS primary_schema, fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
2131
2160
  FROM sqlite_master m
2132
2161
  INNER JOIN pragma_foreign_key_list(m.name) fkl ON m.type = 'table'
2133
2162
  ORDER BY m.name, fkl.seq"
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 105
8
+ TINY = 107
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
@@ -124,6 +124,8 @@ if Gem::Specification.all_names.any? { |g| g.start_with?('rails-') }
124
124
  require 'brick/frameworks/rails'
125
125
  end
126
126
  module Brick
127
+ ALL_API_ACTIONS = [:index, :show, :create, :update, :destroy]
128
+
127
129
  class << self
128
130
  def sti_models
129
131
  @sti_models ||= {}
@@ -347,6 +349,16 @@ module Brick
347
349
  Brick.config.api_roots
348
350
  end
349
351
 
352
+ # @api public
353
+ def api_filter=(proc)
354
+ Brick.config.api_filter = proc
355
+ end
356
+
357
+ # @api public
358
+ def api_filter
359
+ Brick.config.api_filter
360
+ end
361
+
350
362
  # @api public
351
363
  def skip_database_views=(value)
352
364
  Brick.config.skip_database_views = value
@@ -666,7 +678,10 @@ In config/initializers/brick.rb appropriate entries would look something like:
666
678
  ::Brick.relations.each do |k, v|
667
679
  next if !(controller_name = v.fetch(:resource, nil)&.pluralize) || existing_controllers.key?(controller_name)
668
680
 
669
- schema_name = v.fetch(:schema, nil)
681
+ object_name = k.split('.').last # Take off any first schema part
682
+ if (schema_name = v.fetch(:schema, nil))
683
+ schema_prefix = "#{schema_name}."
684
+ end
670
685
  options = {}
671
686
  options[:only] = [:index, :show] if v.key?(:isView)
672
687
  # First do the API routes if necessary
@@ -677,49 +692,111 @@ In config/initializers/brick.rb appropriate entries would look something like:
677
692
  view_relation = nil
678
693
  # If it's a view then see if there's a versioned one available by searching for resource names
679
694
  # versioned with the closest number (equal to or less than) compared with our API version number.
680
- if v.key?(:isView) && (ver = k.match(/^v([\d_]*)/).captures.first)[-1] == '_'
681
- next if api_done_views.key?(unversioned = k[ver.length + 1..-1])
682
-
683
- # # if ().length.positive? # Does it have a version number?
684
- # try_num = (ver_num = (ver = ver[1..-1].gsub('_', '.')).to_d)
695
+ if v.key?(:isView) && (ver = object_name.match(/^v([\d_]*)/)&.captures&.first) && ver[-1] == '_'
696
+ core_object_name = object_name[ver.length + 1..-1]
697
+ next if api_done_views.key?(unversioned = "#{schema_prefix}v_#{core_object_name}")
685
698
 
686
699
  # Expect that the last item in the path generally holds versioning information
687
700
  api_ver = api_root.split('/')[-1]&.gsub('_', '.')
688
701
  vn_idx = api_ver.rindex(/[^\d._]/) # Position of the first numeric digit at the end of the version number
689
702
  # Was: .to_d
690
- api_ver_num = api_ver[vn_idx + 1..-1].gsub('_', '.').to_i # Attempt to turn something like "v3" into the decimal value 3
703
+ test_ver_num = api_ver_num = api_ver[vn_idx + 1..-1].gsub('_', '.').to_i # Attempt to turn something like "v3" into the decimal value 3
691
704
  # puts [api_ver, vn_idx, api_ver_num, unversioned].inspect
692
705
 
693
706
  next if ver.to_i > api_ver_num # Don't surface any newer views in an older API
694
707
 
695
- api_ver_num -= 1 until api_ver_num.zero? ||
696
- (view_relation = ::Brick.relations.fetch(
697
- found = "v#{api_ver_num}_#{k[ver.length + 1..-1]}", nil
698
- ))
708
+ test_ver_num -= 1 until test_ver_num.zero? ||
709
+ (view_relation = ::Brick.relations.fetch(
710
+ found = "#{schema_prefix}v#{test_ver_num}_#{core_object_name}", nil
711
+ ))
699
712
  api_done_views[unversioned] = nil # Mark that for this API version this view is done
700
713
 
701
714
  # puts "Found #{found}" if view_relation
702
715
  # If we haven't found "v3_view_name" or "v2_view_name" or so forth, at the last
703
716
  # fall back to simply looking for "v_view_name", and then finally "view_name".
704
- unversioned = "v_#{unversioned}"
705
- view_relation ||= ::Brick.relations.fetch(found = unversioned,
706
- ::Brick.relations.fetch(found = unversioned, nil)
707
- )
708
- if found && view_relation && k != (found = unversioned)
709
- view_relation[:api][api_ver_num] = found
710
- end
717
+ no_v_prefix_name = "#{schema_prefix}#{core_object_name}"
718
+ else
719
+ unversioned = k
711
720
  end
712
721
 
713
- # view_ver_num = if (first_part = k.split('_').first) =~ /^v[\d_]+/
714
- # first_part[1..-1].gsub('_', '.').to_i
715
- # end
716
- controller_name = view_relation.fetch(:resource, nil)&.pluralize if view_relation
717
- if schema_name
718
- full_resource = "#{schema_name}/#{found || v[:resource]}"
719
- send(:get, "#{api_root}#{full_resource}", { to: "#{controller_prefix}#{schema_name}/#{controller_name}#index" })
720
- else
721
- # Normally goes to something like: /api/v1/employees
722
- send(:get, "#{api_root}#{found || v[:resource]}", { to: "#{controller_prefix}#{controller_name}#index" })
722
+ view_relation ||= ::Brick.relations.fetch(found = unversioned, nil) ||
723
+ (no_v_prefix_name && ::Brick.relations.fetch(found = no_v_prefix_name, nil))
724
+ if view_relation
725
+ actions = view_relation.key?(:isView) ? [:index, :show] : ::Brick::ALL_API_ACTIONS # By default all actions are allowed
726
+ # Call proc that limits which endpoints get surfaced based on version, table or view name, method (get list / get one / post / patch / delete)
727
+ # Returning nil makes it do nothing, false makes it skip creating this endpoint, and an array of up to
728
+ # these 3 things controls and changes the nature of the endpoint that gets built:
729
+ # (updated api_name, name of different relation to route to, allowed actions such as :index, :show, :create, etc)
730
+ proc_result = if (filter = ::Brick.config.api_filter).is_a?(Proc)
731
+ begin
732
+ num_args = filter.arity.negative? ? 6 : filter.arity
733
+ filter.call(*[unversioned, k, actions, api_ver_num, found, test_ver_num][0...num_args])
734
+ rescue StandardError => e
735
+ puts "::Brick.api_filter Proc error: #{e.message}"
736
+ end
737
+ end
738
+ # proc_result expects to receive back: [updated_api_name, to_other_relation, allowed_actions]
739
+
740
+ case proc_result
741
+ when NilClass
742
+ # Do nothing differently than what normal behaviour would be
743
+ when FalseClass # Skip implementing this endpoint
744
+ view_relation[:api][api_ver_num] = nil
745
+ next
746
+ when Array # Did they give back an array of actions?
747
+ unless proc_result.any? { |pr| ::Brick::ALL_API_ACTIONS.exclude?(pr) }
748
+ proc_result = [unversioned, to_relation, proc_result]
749
+ end
750
+ # Otherwise don't change this array because it's probably legit
751
+ when String
752
+ proc_result = [proc_result] # Treat this as the surfaced api_name (path) they want to use for this endpoint
753
+ else
754
+ puts "::Brick.api_filter Proc warning: Unable to parse this result returned: \n #{proc_result.inspect}"
755
+ proc_result = nil # Couldn't understand what in the world was returned
756
+ end
757
+
758
+ if proc_result&.present?
759
+ if proc_result[1] # to_other_relation
760
+ if (new_view_relation = ::Brick.relations.fetch(proc_result[1], nil))
761
+ k = proc_result[1] # Route this call over to this different relation
762
+ view_relation = new_view_relation
763
+ else
764
+ puts "::Brick.api_filter Proc warning: Unable to find new suggested relation with name #{proc_result[1]} -- sticking with #{k} instead."
765
+ end
766
+ end
767
+ if proc_result.first&.!=(k) # updated_api_name -- a different name than this relation would normally have
768
+ found = proc_result.first
769
+ end
770
+ actions &= proc_result[2] if proc_result[2] # allowed_actions
771
+ end
772
+ (view_relation[:api][api_ver_num] ||= {})[unversioned] = actions # Add to the list of API paths this resource responds to
773
+
774
+ # view_ver_num = if (first_part = k.split('_').first) =~ /^v[\d_]+/
775
+ # first_part[1..-1].gsub('_', '.').to_i
776
+ # end
777
+
778
+ controller_name = if (last = view_relation.fetch(:resource, nil)&.pluralize)
779
+ "#{schema_prefix}#{last}"
780
+ else
781
+ found
782
+ end.tr('.', '/')
783
+
784
+ { :index => 'get', :create => 'post' }.each do |action, method|
785
+ if actions.include?(action)
786
+ # Normally goes to something like: /api/v1/employees
787
+ send(method, "#{api_root}#{unversioned.tr('.', '/')}", { to: "#{controller_prefix}#{controller_name}##{action}" })
788
+ end
789
+ end
790
+ # %%% We do not yet surface the #show action
791
+ if (id_col = view_relation[:pk]&.first) # ID-dependent stuff
792
+ { :update => ['put', 'patch'], :destroy => ['delete'] }.each do |action, methods|
793
+ if actions.include?(action)
794
+ methods.each do |method|
795
+ send(method, "#{api_root}#{unversioned.tr('.', '/')}/:#{id_col}", { to: "#{controller_prefix}#{controller_name}##{action}" })
796
+ end
797
+ end
798
+ end
799
+ end
723
800
  end
724
801
  end
725
802
 
@@ -731,7 +808,7 @@ In config/initializers/brick.rb appropriate entries would look something like:
731
808
  else
732
809
  table_class_length = class_name.length if class_name.length > table_class_length
733
810
  tables
734
- end << [class_name, full_resource || v[:resource]]
811
+ end << [class_name, "#{schema_prefix&.tr('.', '/')}#{v[:resource]}"]
735
812
  end
736
813
 
737
814
  # Now the normal routes
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.105
4
+ version: 1.0.107
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorin Thwaits
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-17 00:00:00.000000000 Z
11
+ date: 2023-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -164,6 +164,20 @@ dependencies:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
166
  version: 1.42.0
167
+ - !ruby/object:Gem::Dependency
168
+ name: rswag-ui
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
167
181
  - !ruby/object:Gem::Dependency
168
182
  name: mysql2
169
183
  requirement: !ruby/object:Gem::Requirement