brick 1.0.190 → 1.0.192
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/lib/brick/config.rb +8 -0
 - data/lib/brick/extensions.rb +175 -48
 - data/lib/brick/frameworks/rails/engine.rb +34 -14
 - data/lib/brick/frameworks/rails/form_builder.rb +2 -1
 - data/lib/brick/frameworks/rails/form_tags.rb +13 -4
 - data/lib/brick/frameworks/rails.rb +6 -0
 - data/lib/brick/route_mapper.rb +348 -0
 - data/lib/brick/version_number.rb +1 -1
 - data/lib/brick.rb +10 -335
 - data/lib/generators/brick/controllers_generator.rb +91 -0
 - data/lib/generators/brick/migration_builder.rb +224 -160
 - data/lib/generators/brick/migrations_generator.rb +1 -0
 - data/lib/generators/brick/models_generator.rb +2 -2
 - data/lib/generators/brick/salesforce_migrations_generator.rb +101 -0
 - data/lib/generators/brick/salesforce_schema.rb +105 -0
 - metadata +6 -2
 
    
        data/lib/brick.rb
    CHANGED
    
    | 
         @@ -107,7 +107,7 @@ module Brick 
     | 
|
| 
       107 
107 
     | 
    
         
             
                end
         
     | 
| 
       108 
108 
     | 
    
         | 
| 
       109 
109 
     | 
    
         
             
                attr_accessor :default_schema, :db_schemas, :test_schema,
         
     | 
| 
       110 
     | 
    
         
            -
                              : 
     | 
| 
      
 110 
     | 
    
         
            +
                              :established_drf,
         
     | 
| 
       111 
111 
     | 
    
         
             
                              :is_oracle, :is_eager_loading, :auto_models, :initializer_loaded,
         
     | 
| 
       112 
112 
     | 
    
         
             
                              :table_name_lookup
         
     | 
| 
       113 
113 
     | 
    
         
             
                ::Brick.auto_models = []
         
     | 
| 
         @@ -619,6 +619,10 @@ module Brick 
     | 
|
| 
       619 
619 
     | 
    
         
             
                  Brick.config.license = key
         
     | 
| 
       620 
620 
     | 
    
         
             
                end
         
     | 
| 
       621 
621 
     | 
    
         | 
| 
      
 622 
     | 
    
         
            +
                def omit_empty_tables_in_dropdown=(setting)
         
     | 
| 
      
 623 
     | 
    
         
            +
                  Brick.config.omit_empty_tables_in_dropdown = setting
         
     | 
| 
      
 624 
     | 
    
         
            +
                end
         
     | 
| 
      
 625 
     | 
    
         
            +
             
     | 
| 
       622 
626 
     | 
    
         
             
                def always_load_fields=(field_set)
         
     | 
| 
       623 
627 
     | 
    
         
             
                  Brick.config.always_load_fields = field_set
         
     | 
| 
       624 
628 
     | 
    
         
             
                end
         
     | 
| 
         @@ -877,339 +881,6 @@ In config/initializers/brick.rb appropriate entries would look something like: 
     | 
|
| 
       877 
881 
     | 
    
         
             
                end
         
     | 
| 
       878 
882 
     | 
    
         
             
              end
         
     | 
| 
       879 
883 
     | 
    
         | 
| 
       880 
     | 
    
         
            -
              module RouteMapper
         
     | 
| 
       881 
     | 
    
         
            -
                def add_brick_routes
         
     | 
| 
       882 
     | 
    
         
            -
                  routeset_to_use = ::Rails.application.routes
         
     | 
| 
       883 
     | 
    
         
            -
                  path_prefix = ::Brick.config.path_prefix
         
     | 
| 
       884 
     | 
    
         
            -
                  existing_controllers = routeset_to_use.routes.each_with_object({}) do |r, s|
         
     | 
| 
       885 
     | 
    
         
            -
                    if r.verb == 'GET' && (c = r.defaults[:controller])
         
     | 
| 
       886 
     | 
    
         
            -
                      path = r.path.ast.to_s
         
     | 
| 
       887 
     | 
    
         
            -
                      path = path[0..((path.index('(') || 0) - 1)]
         
     | 
| 
       888 
     | 
    
         
            -
                      # Skip adding this if it's the default_route_fallback set from the initializers/brick.rb file
         
     | 
| 
       889 
     | 
    
         
            -
                      next if "#{path}##{r.defaults[:action]}" == ::Brick.established_drf
         
     | 
| 
       890 
     | 
    
         
            -
             
     | 
| 
       891 
     | 
    
         
            -
                      # next unless [:index, :show, :new, :edit].incude?(a = r.defaults[:action])
         
     | 
| 
       892 
     | 
    
         
            -
             
     | 
| 
       893 
     | 
    
         
            -
                      # c_parts = c.split('/')
         
     | 
| 
       894 
     | 
    
         
            -
                      # while c_parts.length > 0
         
     | 
| 
       895 
     | 
    
         
            -
                      #   c_dotted = c_parts.join('.')
         
     | 
| 
       896 
     | 
    
         
            -
                      #   if (relation = ::Brick.relations[c_dotted]) # Does it match up with an existing Brick table / resource name?
         
     | 
| 
       897 
     | 
    
         
            -
                      #     puts path
         
     | 
| 
       898 
     | 
    
         
            -
                      #     puts "  #{c_dotted}##{r.defaults[:action]}"
         
     | 
| 
       899 
     | 
    
         
            -
                      #     s[c_dotted.tr('.', '/')] = nil
         
     | 
| 
       900 
     | 
    
         
            -
                      #     break
         
     | 
| 
       901 
     | 
    
         
            -
                      #   end
         
     | 
| 
       902 
     | 
    
         
            -
                      #   c_parts.shift
         
     | 
| 
       903 
     | 
    
         
            -
                      # end
         
     | 
| 
       904 
     | 
    
         
            -
                      s[c] = nil
         
     | 
| 
       905 
     | 
    
         
            -
                    end
         
     | 
| 
       906 
     | 
    
         
            -
                  end
         
     | 
| 
       907 
     | 
    
         
            -
             
     | 
| 
       908 
     | 
    
         
            -
                  tables = []
         
     | 
| 
       909 
     | 
    
         
            -
                  views = []
         
     | 
| 
       910 
     | 
    
         
            -
                  table_class_length = 38 # Length of "Classes that can be built from tables:"
         
     | 
| 
       911 
     | 
    
         
            -
                  view_class_length = 37 # Length of "Classes that can be built from views:"
         
     | 
| 
       912 
     | 
    
         
            -
             
     | 
| 
       913 
     | 
    
         
            -
                  brick_namespace_create = lambda do |path_names, res_name, options|
         
     | 
| 
       914 
     | 
    
         
            -
                    if path_names&.present?
         
     | 
| 
       915 
     | 
    
         
            -
                      if (path_name = path_names.pop).is_a?(Array)
         
     | 
| 
       916 
     | 
    
         
            -
                        module_name = path_name[1]
         
     | 
| 
       917 
     | 
    
         
            -
                        path_name = path_name.first
         
     | 
| 
       918 
     | 
    
         
            -
                      end
         
     | 
| 
       919 
     | 
    
         
            -
                      send(:scope, { module: module_name || path_name, path: path_name, as: path_name }) do
         
     | 
| 
       920 
     | 
    
         
            -
                        brick_namespace_create.call(path_names, res_name, options)
         
     | 
| 
       921 
     | 
    
         
            -
                      end
         
     | 
| 
       922 
     | 
    
         
            -
                    else
         
     | 
| 
       923 
     | 
    
         
            -
                      send(:resources, res_name.to_sym, **options)
         
     | 
| 
       924 
     | 
    
         
            -
                    end
         
     | 
| 
       925 
     | 
    
         
            -
                  end
         
     | 
| 
       926 
     | 
    
         
            -
             
     | 
| 
       927 
     | 
    
         
            -
                  # %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
         
     | 
| 
       928 
     | 
    
         
            -
                  # If auto-controllers and auto-models are both enabled then this makes sense:
         
     | 
| 
       929 
     | 
    
         
            -
                  controller_prefix = (path_prefix ? "#{path_prefix}/" : '')
         
     | 
| 
       930 
     | 
    
         
            -
                  sti_subclasses = ::Brick.config.sti_namespace_prefixes.each_with_object(Hash.new { |h, k| h[k] = [] }) do |v, s|
         
     | 
| 
       931 
     | 
    
         
            -
                                      # Turn something like {"::Spouse"=>"Person", "::Friend"=>"Person"} into {"Person"=>["Spouse", "Friend"]}
         
     | 
| 
       932 
     | 
    
         
            -
                                      s[v.last] << v.first[2..-1] unless v.first.end_with?('::')
         
     | 
| 
       933 
     | 
    
         
            -
                                    end
         
     | 
| 
       934 
     | 
    
         
            -
                  versioned_views = {} # Track which views have already been done for each api_root
         
     | 
| 
       935 
     | 
    
         
            -
                  ::Brick.relations.each do |k, v|
         
     | 
| 
       936 
     | 
    
         
            -
                    next if k.is_a?(Symbol)
         
     | 
| 
       937 
     | 
    
         
            -
             
     | 
| 
       938 
     | 
    
         
            -
                    if (schema_name = v.fetch(:schema, nil))
         
     | 
| 
       939 
     | 
    
         
            -
                      schema_prefix = "#{schema_name}."
         
     | 
| 
       940 
     | 
    
         
            -
                    end
         
     | 
| 
       941 
     | 
    
         
            -
             
     | 
| 
       942 
     | 
    
         
            -
                    resource_name = v.fetch(:resource, nil)
         
     | 
| 
       943 
     | 
    
         
            -
                    next if !resource_name ||
         
     | 
| 
       944 
     | 
    
         
            -
                            existing_controllers.key?(
         
     | 
| 
       945 
     | 
    
         
            -
                              controller_prefix + (resource_name = "#{schema_prefix&.tr('.', '/')}#{resource_name}".pluralize)
         
     | 
| 
       946 
     | 
    
         
            -
                            )
         
     | 
| 
       947 
     | 
    
         
            -
             
     | 
| 
       948 
     | 
    
         
            -
                    object_name = k.split('.').last # Take off any first schema part
         
     | 
| 
       949 
     | 
    
         
            -
             
     | 
| 
       950 
     | 
    
         
            -
                    full_schema_prefix = if (full_aps = aps = v.fetch(:auto_prefixed_schema, nil))
         
     | 
| 
       951 
     | 
    
         
            -
                                            aps = aps[0..-2] if aps[-1] == '_'
         
     | 
| 
       952 
     | 
    
         
            -
                                            (schema_prefix&.dup || +'') << "#{aps}."
         
     | 
| 
       953 
     | 
    
         
            -
                                          else
         
     | 
| 
       954 
     | 
    
         
            -
                                            schema_prefix
         
     | 
| 
       955 
     | 
    
         
            -
                                          end
         
     | 
| 
       956 
     | 
    
         
            -
             
     | 
| 
       957 
     | 
    
         
            -
                    # Track routes being built
         
     | 
| 
       958 
     | 
    
         
            -
                    if (class_name = v.fetch(:class_name, nil))
         
     | 
| 
       959 
     | 
    
         
            -
                      if v.key?(:isView)
         
     | 
| 
       960 
     | 
    
         
            -
                        view_class_length = class_name.length if class_name.length > view_class_length
         
     | 
| 
       961 
     | 
    
         
            -
                        views
         
     | 
| 
       962 
     | 
    
         
            -
                      else
         
     | 
| 
       963 
     | 
    
         
            -
                        table_class_length = class_name.length if class_name.length > table_class_length
         
     | 
| 
       964 
     | 
    
         
            -
                        tables
         
     | 
| 
       965 
     | 
    
         
            -
                      end << [class_name, aps, k.tr('.', '/')[full_aps&.length || 0 .. -1]]
         
     | 
| 
       966 
     | 
    
         
            -
                    end
         
     | 
| 
       967 
     | 
    
         
            -
             
     | 
| 
       968 
     | 
    
         
            -
                    options = {}
         
     | 
| 
       969 
     | 
    
         
            -
                    options[:only] = [:index, :show] if v.key?(:isView)
         
     | 
| 
       970 
     | 
    
         
            -
             
     | 
| 
       971 
     | 
    
         
            -
                    # First do the normal routes
         
     | 
| 
       972 
     | 
    
         
            -
                    prefixes = []
         
     | 
| 
       973 
     | 
    
         
            -
                    prefixes << [aps, v[:class_name]&.split('::')[-2]&.underscore] if aps
         
     | 
| 
       974 
     | 
    
         
            -
                    prefixes << schema_name if schema_name
         
     | 
| 
       975 
     | 
    
         
            -
                    prefixes << path_prefix if path_prefix
         
     | 
| 
       976 
     | 
    
         
            -
                    brick_namespace_create.call(prefixes, v[:resource], options)
         
     | 
| 
       977 
     | 
    
         
            -
                    sti_subclasses.fetch(class_name, nil)&.each do |sc| # Add any STI subclass routes for this relation
         
     | 
| 
       978 
     | 
    
         
            -
                      brick_namespace_create.call(prefixes, sc.underscore.tr('/', '_').pluralize, options)
         
     | 
| 
       979 
     | 
    
         
            -
                    end
         
     | 
| 
       980 
     | 
    
         
            -
             
     | 
| 
       981 
     | 
    
         
            -
                    # Now the API routes if necessary
         
     | 
| 
       982 
     | 
    
         
            -
                    full_resource = nil
         
     | 
| 
       983 
     | 
    
         
            -
                    ::Brick.api_roots&.each do |api_root|
         
     | 
| 
       984 
     | 
    
         
            -
                      api_done_views = (versioned_views[api_root] ||= {})
         
     | 
| 
       985 
     | 
    
         
            -
                      found = nil
         
     | 
| 
       986 
     | 
    
         
            -
                      test_ver_num = nil
         
     | 
| 
       987 
     | 
    
         
            -
                      view_relation = nil
         
     | 
| 
       988 
     | 
    
         
            -
                      # If it's a view then see if there's a versioned one available by searching for resource names
         
     | 
| 
       989 
     | 
    
         
            -
                      # versioned with the closest number (equal to or less than) compared with our API version number.
         
     | 
| 
       990 
     | 
    
         
            -
                      if v.key?(:isView)
         
     | 
| 
       991 
     | 
    
         
            -
                        if (ver = object_name.match(/^v([\d_]*)/)&.captures&.first) && ver[-1] == '_'
         
     | 
| 
       992 
     | 
    
         
            -
                          core_object_name = object_name[ver.length + 1..-1]
         
     | 
| 
       993 
     | 
    
         
            -
                          next if api_done_views.key?(unversioned = "#{schema_prefix}v_#{core_object_name}")
         
     | 
| 
       994 
     | 
    
         
            -
             
     | 
| 
       995 
     | 
    
         
            -
                          # Expect that the last item in the path generally holds versioning information
         
     | 
| 
       996 
     | 
    
         
            -
                          api_ver = api_root.split('/')[-1]&.gsub('_', '.')
         
     | 
| 
       997 
     | 
    
         
            -
                          vn_idx = api_ver.rindex(/[^\d._]/) # Position of the first numeric digit at the end of the version number
         
     | 
| 
       998 
     | 
    
         
            -
                          # Was:  .to_d
         
     | 
| 
       999 
     | 
    
         
            -
                          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
         
     | 
| 
       1000 
     | 
    
         
            -
                          # puts [api_ver, vn_idx, api_ver_num, unversioned].inspect
         
     | 
| 
       1001 
     | 
    
         
            -
             
     | 
| 
       1002 
     | 
    
         
            -
                          next if ver.to_i > api_ver_num # Don't surface any newer views in an older API
         
     | 
| 
       1003 
     | 
    
         
            -
             
     | 
| 
       1004 
     | 
    
         
            -
                          test_ver_num -= 1 until test_ver_num.zero? ||
         
     | 
| 
       1005 
     | 
    
         
            -
                                                  (view_relation = ::Brick.relations.fetch(
         
     | 
| 
       1006 
     | 
    
         
            -
                                                    found = "#{schema_prefix}v#{test_ver_num}_#{core_object_name}", nil
         
     | 
| 
       1007 
     | 
    
         
            -
                                                  ))
         
     | 
| 
       1008 
     | 
    
         
            -
                          api_done_views[unversioned] = nil # Mark that for this API version this view is done
         
     | 
| 
       1009 
     | 
    
         
            -
             
     | 
| 
       1010 
     | 
    
         
            -
                          # puts "Found #{found}" if view_relation
         
     | 
| 
       1011 
     | 
    
         
            -
                          # If we haven't found "v3_view_name" or "v2_view_name" or so forth, at the last
         
     | 
| 
       1012 
     | 
    
         
            -
                          # fall back to simply looking for "v_view_name", and then finally  "view_name".
         
     | 
| 
       1013 
     | 
    
         
            -
                          no_v_prefix_name = "#{schema_prefix}#{core_object_name}"
         
     | 
| 
       1014 
     | 
    
         
            -
                          standard_prefix = 'v_'
         
     | 
| 
       1015 
     | 
    
         
            -
                        else
         
     | 
| 
       1016 
     | 
    
         
            -
                          core_object_name = object_name
         
     | 
| 
       1017 
     | 
    
         
            -
                        end
         
     | 
| 
       1018 
     | 
    
         
            -
                        if (rvp = ::Brick.config.api_remove_view_prefix) && core_object_name.start_with?(rvp)
         
     | 
| 
       1019 
     | 
    
         
            -
                          core_object_name.slice!(0, rvp.length)
         
     | 
| 
       1020 
     | 
    
         
            -
                        end
         
     | 
| 
       1021 
     | 
    
         
            -
                        no_prefix_name = "#{schema_prefix}#{core_object_name}"
         
     | 
| 
       1022 
     | 
    
         
            -
                        unversioned = "#{schema_prefix}#{standard_prefix}#{::Brick.config.api_add_view_prefix}#{core_object_name}"
         
     | 
| 
       1023 
     | 
    
         
            -
                      else
         
     | 
| 
       1024 
     | 
    
         
            -
                        unversioned = k
         
     | 
| 
       1025 
     | 
    
         
            -
                      end
         
     | 
| 
       1026 
     | 
    
         
            -
             
     | 
| 
       1027 
     | 
    
         
            -
                      view_relation ||= ::Brick.relations.fetch(found = unversioned, nil) ||
         
     | 
| 
       1028 
     | 
    
         
            -
                                        (no_v_prefix_name && ::Brick.relations.fetch(found = no_v_prefix_name, nil)) ||
         
     | 
| 
       1029 
     | 
    
         
            -
                                        (no_prefix_name && ::Brick.relations.fetch(found = no_prefix_name, nil))
         
     | 
| 
       1030 
     | 
    
         
            -
                      if view_relation
         
     | 
| 
       1031 
     | 
    
         
            -
                        actions = view_relation.key?(:isView) ? [:index, :show] : ::Brick::ALL_API_ACTIONS # By default all actions are allowed
         
     | 
| 
       1032 
     | 
    
         
            -
                        # Call proc that limits which endpoints get surfaced based on version, table or view name, method (get list / get one / post / patch / delete)
         
     | 
| 
       1033 
     | 
    
         
            -
                        # Returning nil makes it do nothing, false makes it skip creating this endpoint, and an array of up to
         
     | 
| 
       1034 
     | 
    
         
            -
                        # these 3 things controls and changes the nature of the endpoint that gets built:
         
     | 
| 
       1035 
     | 
    
         
            -
                        # (updated api_name, name of different relation to route to, allowed actions such as :index, :show, :create, etc)
         
     | 
| 
       1036 
     | 
    
         
            -
                        proc_result = if (filter = ::Brick.config.api_filter).is_a?(Proc)
         
     | 
| 
       1037 
     | 
    
         
            -
                                        begin
         
     | 
| 
       1038 
     | 
    
         
            -
                                          num_args = filter.arity.negative? ? 6 : filter.arity
         
     | 
| 
       1039 
     | 
    
         
            -
                                          filter.call(*[unversioned, k, view_relation, actions, api_ver_num, found, test_ver_num][0...num_args])
         
     | 
| 
       1040 
     | 
    
         
            -
                                        rescue StandardError => e
         
     | 
| 
       1041 
     | 
    
         
            -
                                          puts "::Brick.api_filter Proc error: #{e.message}"
         
     | 
| 
       1042 
     | 
    
         
            -
                                        end
         
     | 
| 
       1043 
     | 
    
         
            -
                                      end
         
     | 
| 
       1044 
     | 
    
         
            -
                        # proc_result expects to receive back: [updated_api_name, to_other_relation, allowed_actions]
         
     | 
| 
       1045 
     | 
    
         
            -
             
     | 
| 
       1046 
     | 
    
         
            -
                        case proc_result
         
     | 
| 
       1047 
     | 
    
         
            -
                        when NilClass
         
     | 
| 
       1048 
     | 
    
         
            -
                          # Do nothing differently than what normal behaviour would be
         
     | 
| 
       1049 
     | 
    
         
            -
                        when FalseClass # Skip implementing this endpoint
         
     | 
| 
       1050 
     | 
    
         
            -
                          view_relation[:api][api_ver_num] = nil
         
     | 
| 
       1051 
     | 
    
         
            -
                          next
         
     | 
| 
       1052 
     | 
    
         
            -
                        when Array # Did they give back an array of actions?
         
     | 
| 
       1053 
     | 
    
         
            -
                          unless proc_result.any? { |pr| ::Brick::ALL_API_ACTIONS.exclude?(pr) }
         
     | 
| 
       1054 
     | 
    
         
            -
                            proc_result = [unversioned, to_relation, proc_result]
         
     | 
| 
       1055 
     | 
    
         
            -
                          end
         
     | 
| 
       1056 
     | 
    
         
            -
                          # Otherwise don't change this array because it's probably legit
         
     | 
| 
       1057 
     | 
    
         
            -
                        when String
         
     | 
| 
       1058 
     | 
    
         
            -
                          proc_result = [proc_result] # Treat this as the surfaced api_name (path) they want to use for this endpoint
         
     | 
| 
       1059 
     | 
    
         
            -
                        else
         
     | 
| 
       1060 
     | 
    
         
            -
                          puts "::Brick.api_filter Proc warning: Unable to parse this result returned: \n  #{proc_result.inspect}"
         
     | 
| 
       1061 
     | 
    
         
            -
                          proc_result = nil # Couldn't understand what in the world was returned
         
     | 
| 
       1062 
     | 
    
         
            -
                        end
         
     | 
| 
       1063 
     | 
    
         
            -
             
     | 
| 
       1064 
     | 
    
         
            -
                        if proc_result&.present?
         
     | 
| 
       1065 
     | 
    
         
            -
                          if proc_result[1] # to_other_relation
         
     | 
| 
       1066 
     | 
    
         
            -
                            if (new_view_relation = ::Brick.relations.fetch(proc_result[1], nil))
         
     | 
| 
       1067 
     | 
    
         
            -
                              k = proc_result[1] # Route this call over to this different relation
         
     | 
| 
       1068 
     | 
    
         
            -
                              view_relation = new_view_relation
         
     | 
| 
       1069 
     | 
    
         
            -
                            else
         
     | 
| 
       1070 
     | 
    
         
            -
                              puts "::Brick.api_filter Proc warning: Unable to find new suggested relation with name #{proc_result[1]} -- sticking with #{k} instead."
         
     | 
| 
       1071 
     | 
    
         
            -
                            end
         
     | 
| 
       1072 
     | 
    
         
            -
                          end
         
     | 
| 
       1073 
     | 
    
         
            -
                          if proc_result.first&.!=(k) # updated_api_name -- a different name than this relation would normally have
         
     | 
| 
       1074 
     | 
    
         
            -
                            found = proc_result.first
         
     | 
| 
       1075 
     | 
    
         
            -
                          end
         
     | 
| 
       1076 
     | 
    
         
            -
                          actions &= proc_result[2] if proc_result[2] # allowed_actions
         
     | 
| 
       1077 
     | 
    
         
            -
                        end
         
     | 
| 
       1078 
     | 
    
         
            -
                        (view_relation[:api][api_ver_num] ||= {})[unversioned] = actions # Add to the list of API paths this resource responds to
         
     | 
| 
       1079 
     | 
    
         
            -
             
     | 
| 
       1080 
     | 
    
         
            -
                        # view_ver_num = if (first_part = k.split('_').first) =~ /^v[\d_]+/
         
     | 
| 
       1081 
     | 
    
         
            -
                        #                  first_part[1..-1].gsub('_', '.').to_i
         
     | 
| 
       1082 
     | 
    
         
            -
                        #                end
         
     | 
| 
       1083 
     | 
    
         
            -
                        controller_name = if (last = view_relation.fetch(:resource, nil)&.pluralize)
         
     | 
| 
       1084 
     | 
    
         
            -
                                            "#{full_schema_prefix}#{last}"
         
     | 
| 
       1085 
     | 
    
         
            -
                                          else
         
     | 
| 
       1086 
     | 
    
         
            -
                                            found
         
     | 
| 
       1087 
     | 
    
         
            -
                                          end.tr('.', '/')
         
     | 
| 
       1088 
     | 
    
         
            -
             
     | 
| 
       1089 
     | 
    
         
            -
                        { :index => 'get', :create => 'post' }.each do |action, method|
         
     | 
| 
       1090 
     | 
    
         
            -
                          if actions.include?(action)
         
     | 
| 
       1091 
     | 
    
         
            -
                            # Normally goes to something like:  /api/v1/employees
         
     | 
| 
       1092 
     | 
    
         
            -
                            send(method, "#{api_root}#{unversioned.tr('.', '/')}", { to: "#{controller_prefix}#{controller_name}##{action}" })
         
     | 
| 
       1093 
     | 
    
         
            -
                          end
         
     | 
| 
       1094 
     | 
    
         
            -
                        end
         
     | 
| 
       1095 
     | 
    
         
            -
                        # %%% We do not yet surface the #show action
         
     | 
| 
       1096 
     | 
    
         
            -
                        if (id_col = view_relation[:pk]&.first) # ID-dependent stuff
         
     | 
| 
       1097 
     | 
    
         
            -
                          { :update => ['put', 'patch'], :destroy => ['delete'] }.each do |action, methods|
         
     | 
| 
       1098 
     | 
    
         
            -
                            if actions.include?(action)
         
     | 
| 
       1099 
     | 
    
         
            -
                              methods.each do |method|
         
     | 
| 
       1100 
     | 
    
         
            -
                                send(method, "#{api_root}#{unversioned.tr('.', '/')}/:#{id_col}", { to: "#{controller_prefix}#{controller_name}##{action}" })
         
     | 
| 
       1101 
     | 
    
         
            -
                              end
         
     | 
| 
       1102 
     | 
    
         
            -
                            end
         
     | 
| 
       1103 
     | 
    
         
            -
                          end
         
     | 
| 
       1104 
     | 
    
         
            -
                        end
         
     | 
| 
       1105 
     | 
    
         
            -
                      end
         
     | 
| 
       1106 
     | 
    
         
            -
                    end
         
     | 
| 
       1107 
     | 
    
         
            -
             
     | 
| 
       1108 
     | 
    
         
            -
                    # Trestle compatibility
         
     | 
| 
       1109 
     | 
    
         
            -
                    if Object.const_defined?('Trestle') && ::Trestle.config.options&.key?(:site_title) &&
         
     | 
| 
       1110 
     | 
    
         
            -
                        !Object.const_defined?("#{(res_name = resource_name.tr('/', '_')).camelize}Admin")
         
     | 
| 
       1111 
     | 
    
         
            -
                      begin
         
     | 
| 
       1112 
     | 
    
         
            -
                        ::Trestle.resource(res_sym = res_name.to_sym, model: class_name&.constantize) do
         
     | 
| 
       1113 
     | 
    
         
            -
                          menu { item res_sym, icon: "fa fa-star" }
         
     | 
| 
       1114 
     | 
    
         
            -
                        end
         
     | 
| 
       1115 
     | 
    
         
            -
                      rescue
         
     | 
| 
       1116 
     | 
    
         
            -
                      end
         
     | 
| 
       1117 
     | 
    
         
            -
                    end
         
     | 
| 
       1118 
     | 
    
         
            -
                  end
         
     | 
| 
       1119 
     | 
    
         
            -
             
     | 
| 
       1120 
     | 
    
         
            -
                  if (named_routes = instance_variable_get(:@set).named_routes).respond_to?(:find)
         
     | 
| 
       1121 
     | 
    
         
            -
                    if ::Brick.config.add_status && (status_as = "#{controller_prefix.tr('/', '_')}brick_status".to_sym)
         
     | 
| 
       1122 
     | 
    
         
            -
                      (
         
     | 
| 
       1123 
     | 
    
         
            -
                        !(status_route = instance_variable_get(:@set).named_routes.find { |route| route.first == status_as }&.last) ||
         
     | 
| 
       1124 
     | 
    
         
            -
                        !status_route.ast.to_s.include?("/#{controller_prefix}brick_status/")
         
     | 
| 
       1125 
     | 
    
         
            -
                      )
         
     | 
| 
       1126 
     | 
    
         
            -
                      get("/#{controller_prefix}brick_status", to: 'brick_gem#status', as: status_as.to_s)
         
     | 
| 
       1127 
     | 
    
         
            -
                    end
         
     | 
| 
       1128 
     | 
    
         
            -
             
     | 
| 
       1129 
     | 
    
         
            -
                    # ::Brick.config.add_schema &&
         
     | 
| 
       1130 
     | 
    
         
            -
                    if (schema_as = "#{controller_prefix.tr('/', '_')}brick_schema".to_sym)
         
     | 
| 
       1131 
     | 
    
         
            -
                      (
         
     | 
| 
       1132 
     | 
    
         
            -
                        !(schema_route = instance_variable_get(:@set).named_routes.find { |route| route.first == schema_as }&.last) ||
         
     | 
| 
       1133 
     | 
    
         
            -
                        !schema_route.ast.to_s.include?("/#{controller_prefix}brick_schema/")
         
     | 
| 
       1134 
     | 
    
         
            -
                      )
         
     | 
| 
       1135 
     | 
    
         
            -
                      post("/#{controller_prefix}brick_schema", to: 'brick_gem#schema_create', as: schema_as.to_s)
         
     | 
| 
       1136 
     | 
    
         
            -
                    end
         
     | 
| 
       1137 
     | 
    
         
            -
             
     | 
| 
       1138 
     | 
    
         
            -
                    if ::Brick.config.add_orphans && (orphans_as = "#{controller_prefix.tr('/', '_')}brick_orphans".to_sym)
         
     | 
| 
       1139 
     | 
    
         
            -
                      (
         
     | 
| 
       1140 
     | 
    
         
            -
                        !(orphans_route = instance_variable_get(:@set).named_routes.find { |route| route.first == orphans_as }&.last) ||
         
     | 
| 
       1141 
     | 
    
         
            -
                        !orphans_route.ast.to_s.include?("/#{controller_prefix}brick_orphans/")
         
     | 
| 
       1142 
     | 
    
         
            -
                      )
         
     | 
| 
       1143 
     | 
    
         
            -
                      get("/#{controller_prefix}brick_orphans", to: 'brick_gem#orphans', as: 'brick_orphans')
         
     | 
| 
       1144 
     | 
    
         
            -
                    end
         
     | 
| 
       1145 
     | 
    
         
            -
                  end
         
     | 
| 
       1146 
     | 
    
         
            -
             
     | 
| 
       1147 
     | 
    
         
            -
                  if instance_variable_get(:@set).named_routes.names.exclude?(:brick_crosstab)
         
     | 
| 
       1148 
     | 
    
         
            -
                    get("/#{controller_prefix}brick_crosstab", to: 'brick_gem#crosstab', as: 'brick_crosstab')
         
     | 
| 
       1149 
     | 
    
         
            -
                    get("/#{controller_prefix}brick_crosstab/data", to: 'brick_gem#crosstab_data')
         
     | 
| 
       1150 
     | 
    
         
            -
                  end
         
     | 
| 
       1151 
     | 
    
         
            -
             
     | 
| 
       1152 
     | 
    
         
            -
                  if ((rswag_ui_present = Object.const_defined?('Rswag::Ui')) &&
         
     | 
| 
       1153 
     | 
    
         
            -
                      (rswag_path = routeset_to_use.routes.find { |r| r.app.app == ::Rswag::Ui::Engine }
         
     | 
| 
       1154 
     | 
    
         
            -
                                                          &.instance_variable_get(:@path_formatter)
         
     | 
| 
       1155 
     | 
    
         
            -
                                                          &.instance_variable_get(:@parts)&.join) &&
         
     | 
| 
       1156 
     | 
    
         
            -
                      (doc_endpoints = ::Rswag::Ui.config.config_object[:urls])) ||
         
     | 
| 
       1157 
     | 
    
         
            -
                     (doc_endpoints = ::Brick.instance_variable_get(:@swagger_endpoints))
         
     | 
| 
       1158 
     | 
    
         
            -
                    last_endpoint_parts = nil
         
     | 
| 
       1159 
     | 
    
         
            -
                    doc_endpoints.each do |doc_endpoint|
         
     | 
| 
       1160 
     | 
    
         
            -
                      puts "Mounting OpenApi 3.0 documentation endpoint for \"#{doc_endpoint[:name]}\" on #{doc_endpoint[:url]}" unless ::Brick.routes_done
         
     | 
| 
       1161 
     | 
    
         
            -
                      send(:get, doc_endpoint[:url], { to: 'brick_openapi#index' })
         
     | 
| 
       1162 
     | 
    
         
            -
                      endpoint_parts = doc_endpoint[:url]&.split('/')
         
     | 
| 
       1163 
     | 
    
         
            -
                      last_endpoint_parts = endpoint_parts
         
     | 
| 
       1164 
     | 
    
         
            -
                    end
         
     | 
| 
       1165 
     | 
    
         
            -
                  end
         
     | 
| 
       1166 
     | 
    
         
            -
                  return if ::Brick.routes_done
         
     | 
| 
       1167 
     | 
    
         
            -
             
     | 
| 
       1168 
     | 
    
         
            -
                  if doc_endpoints.present?
         
     | 
| 
       1169 
     | 
    
         
            -
                    if rswag_ui_present
         
     | 
| 
       1170 
     | 
    
         
            -
                      if rswag_path
         
     | 
| 
       1171 
     | 
    
         
            -
                        puts "API documentation now available when navigating to:  /#{last_endpoint_parts&.find(&:present?)}/index.html"
         
     | 
| 
       1172 
     | 
    
         
            -
                      else
         
     | 
| 
       1173 
     | 
    
         
            -
                        puts "In order to make documentation available you can put this into your routes.rb:"
         
     | 
| 
       1174 
     | 
    
         
            -
                        puts "  mount Rswag::Ui::Engine => '/#{last_endpoint_parts&.find(&:present?) || 'api-docs'}'"
         
     | 
| 
       1175 
     | 
    
         
            -
                      end
         
     | 
| 
       1176 
     | 
    
         
            -
                    else
         
     | 
| 
       1177 
     | 
    
         
            -
                      puts "Having this exposed, one easy way to leverage this to create HTML-based API documentation is to use Scalar.
         
     | 
| 
       1178 
     | 
    
         
            -
            It will jump to life when you put these two lines into a view template or other HTML resource:
         
     | 
| 
       1179 
     | 
    
         
            -
              <script id=\"api-reference\" data-url=\"#{last_endpoint_parts.join('/')}\"></script>
         
     | 
| 
       1180 
     | 
    
         
            -
              <script src=\"https://cdn.jsdelivr.net/@scalar/api-reference\"></script>
         
     | 
| 
       1181 
     | 
    
         
            -
            Alternatively you can add the rswag-ui gem."
         
     | 
| 
       1182 
     | 
    
         
            -
                    end
         
     | 
| 
       1183 
     | 
    
         
            -
                  elsif rswag_ui_present
         
     | 
| 
       1184 
     | 
    
         
            -
                    sample_path = rswag_path || '/api-docs'
         
     | 
| 
       1185 
     | 
    
         
            -
                    puts
         
     | 
| 
       1186 
     | 
    
         
            -
                    puts "Brick:  rswag-ui gem detected -- to make OpenAPI 3.0 documentation available from a path such as  '#{sample_path}/v1/swagger.json',"
         
     | 
| 
       1187 
     | 
    
         
            -
                    puts '        put code such as this in an initializer:'
         
     | 
| 
       1188 
     | 
    
         
            -
                    puts '  Rswag::Ui.configure do |config|'
         
     | 
| 
       1189 
     | 
    
         
            -
                    puts "    config.swagger_endpoint '#{sample_path}/v1/swagger.json', 'API V1 Docs'"
         
     | 
| 
       1190 
     | 
    
         
            -
                    puts '  end'
         
     | 
| 
       1191 
     | 
    
         
            -
                    unless rswag_path
         
     | 
| 
       1192 
     | 
    
         
            -
                      puts
         
     | 
| 
       1193 
     | 
    
         
            -
                      puts '        and put this into your routes.rb:'
         
     | 
| 
       1194 
     | 
    
         
            -
                      puts "  mount Rswag::Ui::Engine => '/api-docs'"
         
     | 
| 
       1195 
     | 
    
         
            -
                    end
         
     | 
| 
       1196 
     | 
    
         
            -
                  end
         
     | 
| 
       1197 
     | 
    
         
            -
             
     | 
| 
       1198 
     | 
    
         
            -
                  puts "\n" if tables.present? || views.present?
         
     | 
| 
       1199 
     | 
    
         
            -
                  if tables.present?
         
     | 
| 
       1200 
     | 
    
         
            -
                    puts "Classes that can be built from tables:#{' ' * (table_class_length - 38)}  Path:"
         
     | 
| 
       1201 
     | 
    
         
            -
                    puts "======================================#{' ' * (table_class_length - 38)}  ====="
         
     | 
| 
       1202 
     | 
    
         
            -
                    ::Brick.display_classes(controller_prefix, tables, table_class_length)
         
     | 
| 
       1203 
     | 
    
         
            -
                  end
         
     | 
| 
       1204 
     | 
    
         
            -
                  if views.present?
         
     | 
| 
       1205 
     | 
    
         
            -
                    puts "Classes that can be built from views:#{' ' * (view_class_length - 37)}  Path:"
         
     | 
| 
       1206 
     | 
    
         
            -
                    puts "=====================================#{' ' * (view_class_length - 37)}  ====="
         
     | 
| 
       1207 
     | 
    
         
            -
                    ::Brick.display_classes(controller_prefix, views, view_class_length)
         
     | 
| 
       1208 
     | 
    
         
            -
                  end
         
     | 
| 
       1209 
     | 
    
         
            -
                  ::Brick.routes_done = true
         
     | 
| 
       1210 
     | 
    
         
            -
                end
         
     | 
| 
       1211 
     | 
    
         
            -
              end
         
     | 
| 
       1212 
     | 
    
         
            -
             
     | 
| 
       1213 
884 
     | 
    
         
             
            end
         
     | 
| 
       1214 
885 
     | 
    
         | 
| 
       1215 
886 
     | 
    
         
             
            require 'brick/version_number'
         
     | 
| 
         @@ -2004,7 +1675,11 @@ end 
     | 
|
| 
       2004 
1675 
     | 
    
         | 
| 
       2005 
1676 
     | 
    
         
             
            # Now the Ransack Polyamorous version of #build
         
     | 
| 
       2006 
1677 
     | 
    
         
             
            if Gem::Dependency.new('ransack').matching_specs.present?
         
     | 
| 
       2007 
     | 
    
         
            -
               
     | 
| 
      
 1678 
     | 
    
         
            +
              begin # First try the new way of requiring Polyamorous
         
     | 
| 
      
 1679 
     | 
    
         
            +
                require 'polyamorous/activerecord/join_dependency'
         
     | 
| 
      
 1680 
     | 
    
         
            +
              rescue LoadError => e
         
     | 
| 
      
 1681 
     | 
    
         
            +
                require "polyamorous/activerecord_#{::ActiveRecord::VERSION::STRING[0, 3]}_ruby_2/join_dependency"
         
     | 
| 
      
 1682 
     | 
    
         
            +
              end
         
     | 
| 
       2008 
1683 
     | 
    
         
             
              module Polyamorous::JoinDependencyExtensions
         
     | 
| 
       2009 
1684 
     | 
    
         
             
                def build(associations, base_klass, root = nil, path = '')
         
     | 
| 
       2010 
1685 
     | 
    
         
             
                  root ||= associations
         
     | 
| 
         @@ -0,0 +1,91 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'brick'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'rails/generators'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'rails/generators/active_record'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'fancy_gets'
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            module Brick
         
     | 
| 
      
 9 
     | 
    
         
            +
              # Auto-generates controllers
         
     | 
| 
      
 10 
     | 
    
         
            +
              class ControllersGenerator < ::Rails::Generators::Base
         
     | 
| 
      
 11 
     | 
    
         
            +
                include FancyGets
         
     | 
| 
      
 12 
     | 
    
         
            +
                # include ::Rails::Generators::Migration
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                desc 'Auto-generates controllers'
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                def brick_controllers
         
     | 
| 
      
 17 
     | 
    
         
            +
                  # %%% If Apartment is active and there's no schema_to_analyse, ask which schema they want
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  ::Brick.mode = :on
         
     | 
| 
      
 20 
     | 
    
         
            +
                  ActiveRecord::Base.establish_connection
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  # Load all models and controllers
         
     | 
| 
      
 23 
     | 
    
         
            +
                  ::Brick.eager_load_classes
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  # Generate a list of viable controllers that can be chosen
         
     | 
| 
      
 26 
     | 
    
         
            +
                  longest_length = 0
         
     | 
| 
      
 27 
     | 
    
         
            +
                  model_info = Hash.new { |h, k| h[k] = {} }
         
     | 
| 
      
 28 
     | 
    
         
            +
                  tableless = Hash.new { |h, k| h[k] = [] }
         
     | 
| 
      
 29 
     | 
    
         
            +
                  existing_controllers = ActionController::Base.descendants.reject do |c|
         
     | 
| 
      
 30 
     | 
    
         
            +
                    c.name.start_with?('Turbo::Native::')
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end.map(&:name)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  controllers = ::Brick.relations.each_with_object([]) do |rel, s|
         
     | 
| 
      
 33 
     | 
    
         
            +
                    next if rel.first.is_a?(Symbol)
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                    tbl_parts = rel.first.split('.')
         
     | 
| 
      
 36 
     | 
    
         
            +
                    tbl_parts.shift if [::Brick.default_schema, 'public'].include?(tbl_parts.first)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    tbl_parts[-1] = tbl_parts[-1].pluralize
         
     | 
| 
      
 38 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 39 
     | 
    
         
            +
                      s << ControllerOption.new(tbl_parts.join('/').camelize, rel.last[:class_name].constantize)
         
     | 
| 
      
 40 
     | 
    
         
            +
                    rescue
         
     | 
| 
      
 41 
     | 
    
         
            +
                    end
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end.reject { |c| existing_controllers.include?(c.to_s) }
         
     | 
| 
      
 43 
     | 
    
         
            +
                  controllers.sort! do |a, b| # Sort first to separate namespaced stuff from the rest, then alphabetically
         
     | 
| 
      
 44 
     | 
    
         
            +
                    is_a_namespaced = a.to_s.include?('::')
         
     | 
| 
      
 45 
     | 
    
         
            +
                    is_b_namespaced = b.to_s.include?('::')
         
     | 
| 
      
 46 
     | 
    
         
            +
                    if is_a_namespaced && !is_b_namespaced
         
     | 
| 
      
 47 
     | 
    
         
            +
                      1
         
     | 
| 
      
 48 
     | 
    
         
            +
                    elsif !is_a_namespaced && is_b_namespaced
         
     | 
| 
      
 49 
     | 
    
         
            +
                      -1
         
     | 
| 
      
 50 
     | 
    
         
            +
                    else
         
     | 
| 
      
 51 
     | 
    
         
            +
                      a.to_s <=> b.to_s
         
     | 
| 
      
 52 
     | 
    
         
            +
                    end
         
     | 
| 
      
 53 
     | 
    
         
            +
                  end
         
     | 
| 
      
 54 
     | 
    
         
            +
                  controllers.each do |m| # Find longest name in the list for future use to show lists on the right side of the screen
         
     | 
| 
      
 55 
     | 
    
         
            +
                    if longest_length < (len = m.to_s.length)
         
     | 
| 
      
 56 
     | 
    
         
            +
                      longest_length = len
         
     | 
| 
      
 57 
     | 
    
         
            +
                    end
         
     | 
| 
      
 58 
     | 
    
         
            +
                  end
         
     | 
| 
      
 59 
     | 
    
         
            +
                  chosen = gets_list(list: controllers, chosen: controllers.dup)
         
     | 
| 
      
 60 
     | 
    
         
            +
                  relations = ::Brick.relations
         
     | 
| 
      
 61 
     | 
    
         
            +
                  chosen.each do |controller_option|
         
     | 
| 
      
 62 
     | 
    
         
            +
                    if (controller_parts = controller_option.to_s.split('::')).length > 1
         
     | 
| 
      
 63 
     | 
    
         
            +
                      namespace = controller_parts.first.constantize
         
     | 
| 
      
 64 
     | 
    
         
            +
                    end
         
     | 
| 
      
 65 
     | 
    
         
            +
                    _built_controller, code = Object.send(:build_controller, namespace, controller_parts.last, controller_parts.last.pluralize, controller_option.model, relations)
         
     | 
| 
      
 66 
     | 
    
         
            +
                    path = ['controllers']
         
     | 
| 
      
 67 
     | 
    
         
            +
                    path.concat(controller_parts.map(&:underscore))
         
     | 
| 
      
 68 
     | 
    
         
            +
                    dir = +"#{::Rails.root}/app"
         
     | 
| 
      
 69 
     | 
    
         
            +
                    path[0..-2].each do |path_part|
         
     | 
| 
      
 70 
     | 
    
         
            +
                      dir << "/#{path_part}"
         
     | 
| 
      
 71 
     | 
    
         
            +
                      Dir.mkdir(dir) unless Dir.exist?(dir)
         
     | 
| 
      
 72 
     | 
    
         
            +
                    end
         
     | 
| 
      
 73 
     | 
    
         
            +
                    File.open("#{dir}/#{path.last}.rb", 'w') { |f| f.write code } unless code.blank?
         
     | 
| 
      
 74 
     | 
    
         
            +
                  end
         
     | 
| 
      
 75 
     | 
    
         
            +
                  puts "\n*** Created #{chosen.length} controller files under app/controllers ***"
         
     | 
| 
      
 76 
     | 
    
         
            +
                end
         
     | 
| 
      
 77 
     | 
    
         
            +
              end
         
     | 
| 
      
 78 
     | 
    
         
            +
            end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
            class ControllerOption
         
     | 
| 
      
 81 
     | 
    
         
            +
              attr_accessor :name, :model
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
              def initialize(name, model)
         
     | 
| 
      
 84 
     | 
    
         
            +
                self.name = name
         
     | 
| 
      
 85 
     | 
    
         
            +
                self.model = model
         
     | 
| 
      
 86 
     | 
    
         
            +
              end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
              def to_s
         
     | 
| 
      
 89 
     | 
    
         
            +
                name
         
     | 
| 
      
 90 
     | 
    
         
            +
              end
         
     | 
| 
      
 91 
     | 
    
         
            +
            end
         
     |