rest_framework 0.5.1 → 0.5.4
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/README.md +4 -4
- data/VERSION +1 -1
- data/app/views/rest_framework/_route.html.erb +8 -6
- data/app/views/rest_framework/_routes.html.erb +1 -1
- data/lib/rest_framework/controller_mixins/base.rb +0 -2
- data/lib/rest_framework/controller_mixins/models.rb +1 -2
- data/lib/rest_framework/filters.rb +3 -3
- data/lib/rest_framework/routers.rb +4 -4
- data/lib/rest_framework/serializers.rb +81 -35
- data/lib/rest_framework/utils.rb +55 -15
- data/lib/rest_framework/version.rb +9 -3
- metadata +2 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 413ee55f2eecbb8a8cdd3b133852d83a3e3fdb7e43f19c8deae12adcb3d38977
         | 
| 4 | 
            +
              data.tar.gz: 6582a4d8acd988ad3034af3191b75454fcf602c5f968772e18fa9d5535b71dea
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 19cb82822923617face6c76e0fd36cfa001f23e88393210990842563b0483891dab03638e11c65bf1182db6ebe0d164cf09bb3e35a7ea06b9769c729e16b85d5
         | 
| 7 | 
            +
              data.tar.gz: fc853858e022b126d67f878c63f2e46e18a1f03d03e2aeda90f7c8996b6a461bd41d41d44fadb59d46256aa9c35b18efc1c5e9b7970f712dce010af0c9178e68
         | 
    
        data/README.md
    CHANGED
    
    | @@ -120,9 +120,9 @@ using RVM. Then run `bundle install` to install the appropriate gems. | |
| 120 120 | 
             
            To run the test suite:
         | 
| 121 121 |  | 
| 122 122 | 
             
            ```shell
         | 
| 123 | 
            -
            $  | 
| 123 | 
            +
            $ rails test
         | 
| 124 124 | 
             
            ```
         | 
| 125 125 |  | 
| 126 | 
            -
             | 
| 127 | 
            -
            run ` | 
| 128 | 
            -
             | 
| 126 | 
            +
            The top-level `bin/rails` proxies all Rails commands to the test project, so you can operate it via
         | 
| 127 | 
            +
            the usual commands. Ensure you run `rails db:setup` before running `rails server` or
         | 
| 128 | 
            +
            `rails console`.
         | 
    
        data/VERSION
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            0.5. | 
| 1 | 
            +
            0.5.4
         | 
| @@ -1,9 +1,11 @@ | |
| 1 1 | 
             
            <tr>
         | 
| 2 | 
            -
              <td | 
| 2 | 
            +
              <td>
         | 
| 3 | 
            +
                <% if route[:route].name && link_args = route[:show_link_args] %>
         | 
| 4 | 
            +
                  <%= link_to route[:relative_path], self.send("#{route[:route].name}_path", *link_args) %>
         | 
| 5 | 
            +
                <% else %>
         | 
| 6 | 
            +
                  <%= route[:relative_path] %>
         | 
| 7 | 
            +
                <% end %>
         | 
| 8 | 
            +
              </td>
         | 
| 3 9 | 
             
              <td><%= route[:verb] %></td>
         | 
| 4 | 
            -
               | 
| 5 | 
            -
                <td><%= route[:controller] %>#<%= route[:action] %></td>
         | 
| 6 | 
            -
              <% else %>
         | 
| 7 | 
            -
                <td><%= route[:route_app] %></td>
         | 
| 8 | 
            -
              <% end %>
         | 
| 10 | 
            +
              <td><%= route[:controller] %>#<%= route[:action] %></td>
         | 
| 9 11 | 
             
            </tr>
         | 
| @@ -31,6 +31,7 @@ module RESTFramework::BaseModelControllerMixin | |
| 31 31 | 
             
                    native_serializer_config: nil,
         | 
| 32 32 | 
             
                    native_serializer_singular_config: nil,
         | 
| 33 33 | 
             
                    native_serializer_plural_config: nil,
         | 
| 34 | 
            +
                    native_serializer_only_query_param: "only",
         | 
| 34 35 | 
             
                    native_serializer_except_query_param: "except",
         | 
| 35 36 |  | 
| 36 37 | 
             
                    # Attributes for default model filtering (and ordering).
         | 
| @@ -57,8 +58,6 @@ module RESTFramework::BaseModelControllerMixin | |
| 57 58 | 
             
                end
         | 
| 58 59 | 
             
              end
         | 
| 59 60 |  | 
| 60 | 
            -
              protected
         | 
| 61 | 
            -
             | 
| 62 61 | 
             
              def _get_specific_action_config(action_config_key, generic_config_key)
         | 
| 63 62 | 
             
                action_config = self.class.send(action_config_key) || {}
         | 
| 64 63 | 
             
                action = self.action_name&.to_sym
         | 
| @@ -13,7 +13,7 @@ end | |
| 13 13 | 
             
            class RESTFramework::ModelFilter < RESTFramework::BaseFilter
         | 
| 14 14 | 
             
              # Filter params for keys allowed by the current action's filterset_fields/fields config.
         | 
| 15 15 | 
             
              def _get_filter_params
         | 
| 16 | 
            -
                fields = @controller. | 
| 16 | 
            +
                fields = @controller.get_filterset_fields
         | 
| 17 17 | 
             
                return @controller.request.query_parameters.select { |p, _|
         | 
| 18 18 | 
             
                  fields.include?(p)
         | 
| 19 19 | 
             
                }.to_h.symbolize_keys  # convert from HashWithIndifferentAccess to Hash w/keys
         | 
| @@ -36,7 +36,7 @@ class RESTFramework::ModelOrderingFilter < RESTFramework::BaseFilter | |
| 36 36 | 
             
              def _get_ordering
         | 
| 37 37 | 
             
                return nil if @controller.class.ordering_query_param.blank?
         | 
| 38 38 |  | 
| 39 | 
            -
                ordering_fields = @controller. | 
| 39 | 
            +
                ordering_fields = @controller.get_ordering_fields
         | 
| 40 40 | 
             
                order_string = @controller.params[@controller.class.ordering_query_param]
         | 
| 41 41 |  | 
| 42 42 | 
             
                unless order_string.blank?
         | 
| @@ -76,7 +76,7 @@ end | |
| 76 76 | 
             
            class RESTFramework::ModelSearchFilter < RESTFramework::BaseFilter
         | 
| 77 77 | 
             
              # Filter data according to the request query parameters.
         | 
| 78 78 | 
             
              def get_filtered_data(data)
         | 
| 79 | 
            -
                fields = @controller. | 
| 79 | 
            +
                fields = @controller.get_search_fields
         | 
| 80 80 | 
             
                search = @controller.request.query_parameters[@controller.class.search_query_param]
         | 
| 81 81 |  | 
| 82 82 | 
             
                # Ensure we use array conditions to prevent SQL injection.
         | 
| @@ -4,7 +4,7 @@ require_relative "utils" | |
| 4 4 | 
             
            module ActionDispatch::Routing
         | 
| 5 5 | 
             
              class Mapper
         | 
| 6 6 | 
             
                # Internal interface to get the controller class from the name and current scope.
         | 
| 7 | 
            -
                 | 
| 7 | 
            +
                def _get_controller_class(name, pluralize: true, fallback_reverse_pluralization: true)
         | 
| 8 8 | 
             
                  # Get class name.
         | 
| 9 9 | 
             
                  name = name.to_s.camelize  # camelize to leave plural names plural
         | 
| 10 10 | 
             
                  name = name.pluralize if pluralize
         | 
| @@ -18,7 +18,7 @@ module ActionDispatch::Routing | |
| 18 18 |  | 
| 19 19 | 
             
                  # Get scope for the class.
         | 
| 20 20 | 
             
                  if @scope[:module]
         | 
| 21 | 
            -
                    mod = @scope[:module].to_s. | 
| 21 | 
            +
                    mod = @scope[:module].to_s.camelize.constantize
         | 
| 22 22 | 
             
                  else
         | 
| 23 23 | 
             
                    mod = Object
         | 
| 24 24 | 
             
                  end
         | 
| @@ -38,7 +38,7 @@ module ActionDispatch::Routing | |
| 38 38 | 
             
                end
         | 
| 39 39 |  | 
| 40 40 | 
             
                # Interal interface for routing extra actions.
         | 
| 41 | 
            -
                 | 
| 41 | 
            +
                def _route_extra_actions(actions, &block)
         | 
| 42 42 | 
             
                  actions.each do |action_config|
         | 
| 43 43 | 
             
                    action_config[:methods].each do |m|
         | 
| 44 44 | 
             
                      public_send(m, action_config[:path], **action_config[:kwargs])
         | 
| @@ -52,7 +52,7 @@ module ActionDispatch::Routing | |
| 52 52 | 
             
                #   not otherwise defined by the controller
         | 
| 53 53 | 
             
                # @param name [Symbol] the resource name, from which path and controller are deduced by default
         | 
| 54 54 | 
             
                # @param skip_undefined [Boolean] whether we should skip routing undefined resourceful actions
         | 
| 55 | 
            -
                 | 
| 55 | 
            +
                def _rest_resources(default_singular, name, skip_undefined: true, **kwargs, &block)
         | 
| 56 56 | 
             
                  controller = kwargs.delete(:controller) || name
         | 
| 57 57 | 
             
                  if controller.is_a?(Class)
         | 
| 58 58 | 
             
                    controller_class = controller
         | 
| @@ -43,7 +43,7 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer | |
| 43 43 | 
             
                @model ||= @object[0].class if
         | 
| 44 44 | 
             
                  @many && @object.is_a?(Enumerable) && @object.is_a?(ActiveRecord::Base)
         | 
| 45 45 |  | 
| 46 | 
            -
                @model ||= @controller. | 
| 46 | 
            +
                @model ||= @controller.get_model if @controller
         | 
| 47 47 | 
             
              end
         | 
| 48 48 |  | 
| 49 49 | 
             
              # Get controller action, if possible.
         | 
| @@ -84,57 +84,103 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer | |
| 84 84 | 
             
              end
         | 
| 85 85 |  | 
| 86 86 | 
             
              # Helper to filter (mutate) a single subconfig for specific keys.
         | 
| 87 | 
            -
              def self. | 
| 88 | 
            -
                return  | 
| 89 | 
            -
             | 
| 90 | 
            -
                if  | 
| 91 | 
            -
             | 
| 92 | 
            -
             | 
| 93 | 
            -
             | 
| 94 | 
            -
             | 
| 95 | 
            -
             | 
| 96 | 
            -
             | 
| 87 | 
            +
              def self.filter_subcfg(subcfg, except: nil, only: nil, additive: false)
         | 
| 88 | 
            +
                return subcfg unless except || only
         | 
| 89 | 
            +
                return subcfg unless subcfg || additive
         | 
| 90 | 
            +
                raise "Cannot pass `only` and `additive` to filter_subcfg." if only && additive
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                if subcfg.is_a?(Array)
         | 
| 93 | 
            +
                  subcfg = subcfg.map(&:to_sym)
         | 
| 94 | 
            +
                  if except
         | 
| 95 | 
            +
                    if additive
         | 
| 96 | 
            +
                      # Only add fields which are not already included.
         | 
| 97 | 
            +
                      subcfg += except - subcfg
         | 
| 98 | 
            +
                    else
         | 
| 99 | 
            +
                      subcfg -= except
         | 
| 100 | 
            +
                    end
         | 
| 101 | 
            +
                  elsif only
         | 
| 102 | 
            +
                    # Ignore `additive` in an `only` context, since it could causing data leaking.
         | 
| 103 | 
            +
                    unless additive
         | 
| 104 | 
            +
                      subcfg.select! { |c| c.in?(only) }
         | 
| 105 | 
            +
                    end
         | 
| 97 106 | 
             
                  end
         | 
| 98 | 
            -
                elsif  | 
| 107 | 
            +
                elsif subcfg.is_a?(Hash)
         | 
| 99 108 | 
             
                  # Additive doesn't make sense in a hash context since we wouldn't know the values.
         | 
| 100 109 | 
             
                  unless additive
         | 
| 101 | 
            -
                     | 
| 102 | 
            -
             | 
| 110 | 
            +
                    if except
         | 
| 111 | 
            +
                      subcfg.symbolize_keys!
         | 
| 112 | 
            +
                      subcfg.reject! { |k, _v| k.in?(except) }
         | 
| 113 | 
            +
                    elsif only
         | 
| 114 | 
            +
                      subcfg.symbolize_keys!
         | 
| 115 | 
            +
                      subcfg.select! { |k, _v| k.in?(only) }
         | 
| 116 | 
            +
                    end
         | 
| 103 117 | 
             
                  end
         | 
| 104 | 
            -
                elsif ! | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
                   | 
| 108 | 
            -
             | 
| 109 | 
            -
                   | 
| 110 | 
            -
             | 
| 118 | 
            +
                elsif !subcfg
         | 
| 119 | 
            +
                  if additive && except
         | 
| 120 | 
            +
                    subcfg = except
         | 
| 121 | 
            +
                  end
         | 
| 122 | 
            +
                else  # Subcfg is a single element (assume string/symbol).
         | 
| 123 | 
            +
                  subcfg = subcfg.to_sym
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                  if except
         | 
| 126 | 
            +
                    if subcfg.in?(except)
         | 
| 127 | 
            +
                      subcfg = [] unless additive
         | 
| 128 | 
            +
                    elsif additive
         | 
| 129 | 
            +
                      subcfg = [subcfg, *except]
         | 
| 130 | 
            +
                    end
         | 
| 131 | 
            +
                  elsif only && !additive && !subcfg.in?(only)  # Protect only/additive data-leaking.
         | 
| 132 | 
            +
                    subcfg = []
         | 
| 111 133 | 
             
                  end
         | 
| 112 134 | 
             
                end
         | 
| 113 135 |  | 
| 114 | 
            -
                return  | 
| 136 | 
            +
                return subcfg
         | 
| 115 137 | 
             
              end
         | 
| 116 138 |  | 
| 117 139 | 
             
              # Helper to filter out configuration properties based on the :except query parameter.
         | 
| 118 | 
            -
              def filter_except( | 
| 119 | 
            -
                return  | 
| 140 | 
            +
              def filter_except(cfg)
         | 
| 141 | 
            +
                return cfg unless @controller
         | 
| 120 142 |  | 
| 121 | 
            -
                 | 
| 122 | 
            -
                 | 
| 143 | 
            +
                except_param = @controller.class.try(:native_serializer_except_query_param)
         | 
| 144 | 
            +
                only_param = @controller.class.try(:native_serializer_only_query_param)
         | 
| 145 | 
            +
                if except_param && except = @controller.request.query_parameters[except_param].presence
         | 
| 123 146 | 
             
                  except = except.split(",").map(&:strip).map(&:to_sym)
         | 
| 124 147 |  | 
| 125 148 | 
             
                  unless except.empty?
         | 
| 126 | 
            -
                    # Duplicate the  | 
| 127 | 
            -
                     | 
| 149 | 
            +
                    # Duplicate the cfg to avoid mutating class state.
         | 
| 150 | 
            +
                    cfg = cfg.deep_dup
         | 
| 128 151 |  | 
| 129 152 | 
             
                    # Filter `only`, `except` (additive), `include`, and `methods`.
         | 
| 130 | 
            -
                     | 
| 131 | 
            -
             | 
| 132 | 
            -
                     | 
| 133 | 
            -
             | 
| 153 | 
            +
                    if cfg[:only]
         | 
| 154 | 
            +
                      cfg[:only] = self.class.filter_subcfg(cfg[:only], except: except)
         | 
| 155 | 
            +
                    else
         | 
| 156 | 
            +
                      cfg[:except] = self.class.filter_subcfg(cfg[:except], except: except, additive: true)
         | 
| 157 | 
            +
                    end
         | 
| 158 | 
            +
                    cfg[:include] = self.class.filter_subcfg(cfg[:include], except: except)
         | 
| 159 | 
            +
                    cfg[:methods] = self.class.filter_subcfg(cfg[:methods], except: except)
         | 
| 160 | 
            +
                  end
         | 
| 161 | 
            +
                elsif only_param && only = @controller.request.query_parameters[only_param].presence
         | 
| 162 | 
            +
                  only = only.split(",").map(&:strip).map(&:to_sym)
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                  unless only.empty?
         | 
| 165 | 
            +
                    # Duplicate the cfg to avoid mutating class state.
         | 
| 166 | 
            +
                    cfg = cfg.deep_dup
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                    # For the `except` part of the serializer, we need to append any columns not in `only`.
         | 
| 169 | 
            +
                    model = @controller.get_model
         | 
| 170 | 
            +
                    except_cols = model&.column_names&.map(&:to_sym)&.reject { |c| c.in?(only) }
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                    # Filter `only`, `except` (additive), `include`, and `methods`.
         | 
| 173 | 
            +
                    if cfg[:only]
         | 
| 174 | 
            +
                      cfg[:only] = self.class.filter_subcfg(cfg[:only], only: only)
         | 
| 175 | 
            +
                    else
         | 
| 176 | 
            +
                      cfg[:except] = self.class.filter_subcfg(cfg[:except], except: except_cols, additive: true)
         | 
| 177 | 
            +
                    end
         | 
| 178 | 
            +
                    cfg[:include] = self.class.filter_subcfg(cfg[:include], only: only)
         | 
| 179 | 
            +
                    cfg[:methods] = self.class.filter_subcfg(cfg[:methods], only: only)
         | 
| 134 180 | 
             
                  end
         | 
| 135 181 | 
             
                end
         | 
| 136 182 |  | 
| 137 | 
            -
                return  | 
| 183 | 
            +
                return cfg
         | 
| 138 184 | 
             
              end
         | 
| 139 185 |  | 
| 140 186 | 
             
              # Get the raw serializer config.
         | 
| @@ -150,7 +196,7 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer | |
| 150 196 | 
             
                end
         | 
| 151 197 |  | 
| 152 198 | 
             
                # If the config wasn't determined, build a serializer config from model fields.
         | 
| 153 | 
            -
                fields = @controller. | 
| 199 | 
            +
                fields = @controller.get_fields if @controller
         | 
| 154 200 | 
             
                if fields
         | 
| 155 201 | 
             
                  if @model
         | 
| 156 202 | 
             
                    columns, methods = fields.partition { |f| f.in?(@model.column_names) }
         | 
| @@ -168,7 +214,7 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer | |
| 168 214 |  | 
| 169 215 | 
             
              # Get a configuration passable to `serializable_hash` for the object, filtered if required.
         | 
| 170 216 | 
             
              def get_serializer_config
         | 
| 171 | 
            -
                return filter_except(self._get_raw_serializer_config)
         | 
| 217 | 
            +
                return @_serializer_config ||= filter_except(self._get_raw_serializer_config)
         | 
| 172 218 | 
             
              end
         | 
| 173 219 |  | 
| 174 220 | 
             
              def serialize(**kwargs)
         | 
    
        data/lib/rest_framework/utils.rb
    CHANGED
    
    | @@ -1,4 +1,6 @@ | |
| 1 1 | 
             
            module RESTFramework::Utils
         | 
| 2 | 
            +
              HTTP_METHOD_ORDERING = %w(GET POST PUT PATCH DELETE)
         | 
| 3 | 
            +
             | 
| 2 4 | 
             
              # Helper to take extra_actions hash and convert to a consistent format:
         | 
| 3 5 | 
             
              # `{paths:, methods:, kwargs:}`.
         | 
| 4 6 | 
             
              def self.parse_extra_actions(extra_actions)
         | 
| @@ -35,30 +37,68 @@ module RESTFramework::Utils | |
| 35 37 | 
             
                end
         | 
| 36 38 | 
             
              end
         | 
| 37 39 |  | 
| 38 | 
            -
              # Helper to get the  | 
| 39 | 
            -
              def self. | 
| 40 | 
            -
                application_routes.router.recognize(request)  | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 40 | 
            +
              # Helper to get the first route pattern which matches the given request.
         | 
| 41 | 
            +
              def self.get_request_route(application_routes, request)
         | 
| 42 | 
            +
                application_routes.router.recognize(request) { |route, _| return route }
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              # Helper to get the route pattern for a route, stripped of the `(:format)` segment.
         | 
| 46 | 
            +
              def self.get_route_pattern(route)
         | 
| 47 | 
            +
                return route.path.spec.to_s.gsub(/\(\.:format\)$/, "")
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              # Helper to normalize a path pattern by replacing URL params with generic placeholder, and
         | 
| 51 | 
            +
              # removing the `(.:format)` at the end.
         | 
| 52 | 
            +
              def self.normalize_path(path)
         | 
| 53 | 
            +
                return path.gsub("(.:format)", "").gsub(/:[0-9A-Za-z_-]+/, ":x")
         | 
| 43 54 | 
             
              end
         | 
| 44 55 |  | 
| 45 | 
            -
              # Helper for showing routes under a controller action | 
| 46 | 
            -
              def self.get_routes(application_routes, request)
         | 
| 47 | 
            -
                 | 
| 48 | 
            -
                 | 
| 56 | 
            +
              # Helper for showing routes under a controller action; used for the browsable API.
         | 
| 57 | 
            +
              def self.get_routes(application_routes, request, current_route: nil)
         | 
| 58 | 
            +
                current_route ||= self.get_request_route(application_routes, request)
         | 
| 59 | 
            +
                current_path = current_route.path.spec.to_s
         | 
| 60 | 
            +
                current_levels = current_path.count("/")
         | 
| 61 | 
            +
                current_normalized_path = self.normalize_path(current_path)
         | 
| 49 62 |  | 
| 50 | 
            -
                # Return routes that match our current route subdomain/pattern, grouped by controller.
         | 
| 63 | 
            +
                # Return routes that match our current route subdomain/pattern, grouped by controller. We
         | 
| 64 | 
            +
                # precompute certain properties of the route for performance.
         | 
| 51 65 | 
             
                return application_routes.routes.map { |r|
         | 
| 66 | 
            +
                  path = r.path.spec.to_s
         | 
| 67 | 
            +
                  levels = path.count("/")
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  # Show link if the route is GET and our current route has all required URL params.
         | 
| 70 | 
            +
                  if r.verb == "GET" && r.path.required_names.length == current_route.path.required_names.length
         | 
| 71 | 
            +
                    show_link_args = current_route.path.required_names.map { |n| request.params[n] }.compact
         | 
| 72 | 
            +
                  else
         | 
| 73 | 
            +
                    show_link_args = nil
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
             | 
| 52 76 | 
             
                  {
         | 
| 77 | 
            +
                    route: r,
         | 
| 53 78 | 
             
                    verb: r.verb,
         | 
| 54 | 
            -
                    path:  | 
| 55 | 
            -
                     | 
| 79 | 
            +
                    path: path,
         | 
| 80 | 
            +
                    normalized_path: self.normalize_path(path),
         | 
| 81 | 
            +
                    relative_path: path.split("/")[current_levels..]&.join("/"),
         | 
| 56 82 | 
             
                    controller: r.defaults[:controller].presence,
         | 
| 83 | 
            +
                    action: r.defaults[:action].presence,
         | 
| 57 84 | 
             
                    subdomain: r.defaults[:subdomain].presence,
         | 
| 58 | 
            -
                     | 
| 85 | 
            +
                    levels: levels,
         | 
| 86 | 
            +
                    show_link_args: show_link_args,
         | 
| 59 87 | 
             
                  }
         | 
| 60 88 | 
             
                }.select { |r|
         | 
| 61 | 
            -
                   | 
| 62 | 
            -
             | 
| 89 | 
            +
                  (
         | 
| 90 | 
            +
                    r[:subdomain] == request.subdomain.presence &&
         | 
| 91 | 
            +
                    r[:normalized_path].start_with?(current_normalized_path) &&
         | 
| 92 | 
            +
                    r[:controller] &&
         | 
| 93 | 
            +
                    r[:action]
         | 
| 94 | 
            +
                  )
         | 
| 95 | 
            +
                }.sort_by { |r|
         | 
| 96 | 
            +
                  # Sort by levels first, so the routes matching closely with current request show first, then
         | 
| 97 | 
            +
                  # by the path, and finally by the HTTP verb.
         | 
| 98 | 
            +
                  [r[:levels], r[:path], HTTP_METHOD_ORDERING.index(r[:verb]) || 99]
         | 
| 99 | 
            +
                }.group_by { |r| r[:controller] }.sort_by { |c, _r|
         | 
| 100 | 
            +
                  # Sort the controller groups by current controller first, then depth, then alphanumerically.
         | 
| 101 | 
            +
                  [request.params[:controller] == c ? 0 : 1, c.count("/"), c]
         | 
| 102 | 
            +
                }.to_h
         | 
| 63 103 | 
             
              end
         | 
| 64 104 | 
             
            end
         | 
| @@ -6,19 +6,25 @@ module RESTFramework | |
| 6 6 | 
             
                def self.get_version(skip_git: false)
         | 
| 7 7 | 
             
                  # First, attempt to get the version from git.
         | 
| 8 8 | 
             
                  unless skip_git
         | 
| 9 | 
            -
                    version = `git describe --dirty  | 
| 9 | 
            +
                    version = `git describe --dirty 2>/dev/null`&.strip
         | 
| 10 10 | 
             
                    return version unless !version || version.empty?
         | 
| 11 11 | 
             
                  end
         | 
| 12 12 |  | 
| 13 13 | 
             
                  # Git failed or was skipped, so try to find a VERSION file.
         | 
| 14 14 | 
             
                  begin
         | 
| 15 15 | 
             
                    version = File.read(VERSION_FILEPATH)&.strip
         | 
| 16 | 
            -
                    return version unless !version || version. | 
| 16 | 
            +
                    return version unless !version || version.empty?
         | 
| 17 17 | 
             
                  rescue SystemCallError
         | 
| 18 18 | 
             
                  end
         | 
| 19 19 |  | 
| 20 | 
            +
                  # If that fails, then try to get a plain commit SHA from git.
         | 
| 21 | 
            +
                  unless skip_git
         | 
| 22 | 
            +
                    version = `git describe --dirty --always`&.strip
         | 
| 23 | 
            +
                    return "0.#{version}" unless !version || version.empty?
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 20 26 | 
             
                  # No VERSION file, so version is unknown.
         | 
| 21 | 
            -
                  return "unknown"
         | 
| 27 | 
            +
                  return "0.unknown"
         | 
| 22 28 | 
             
                end
         | 
| 23 29 |  | 
| 24 30 | 
             
                def self.stamp_version
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: rest_framework
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.5. | 
| 4 | 
            +
              version: 0.5.4
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Gregory N. Schmit
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2022-03- | 
| 11 | 
            +
            date: 2022-03-14 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: rails
         |