rest_framework 0.8.3 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4a54e0539466e7981ab8ba95a1877ad705b46279309c1d3bfd2cfd038e916b2b
4
- data.tar.gz: c26e553c6b4b5435545ffb5fa71c59720f42303347b35336077c764b00fd889d
3
+ metadata.gz: 8bbc760b6f4694137002e90cf81453f1e07cd86ffd69a8318646db54b31144b6
4
+ data.tar.gz: 41ce57cbee20b11453d332e03fbdf54118ba7b7f1ceeaa5b3b02d118675c6a3d
5
5
  SHA512:
6
- metadata.gz: 79a80f7d7b3ab044203b612fc8ed453c3a7490a17e9e53adb8386719b9d6944838e78ce343d3596ac8c370cc6dc122d2ede66ec2afce7ffaa9dc635ed23146ad
7
- data.tar.gz: 2943c3779a404ff67f64f6c38001c9de828f3f536c79da1663b02dad0f4c6b20fa36fcd0e4310cbffda48dc4db6c329d09c5e7c48d93e62dc53a157b86e75eef
6
+ metadata.gz: 59fdf66ab6dd1c457a9551ed1a9ab0724fc2079be1c28ec2ebc08eada94390fe66e3722ab278a6d5fba7c55aed9a4a75dca0e3ea498cd183832d70553f6db9d7
7
+ data.tar.gz: e3e2f6b3c3ff29e03d8c9c3a058996bea5de44acfae449a065278a000b98b22b536892dd2d4e3e46b65d9c94cee85aedd1f9928e697e39afd4c491b33ae33504
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.8.3
1
+ 0.8.5
@@ -112,7 +112,7 @@ module RESTFramework::BaseControllerMixin
112
112
 
113
113
  # Get a hash of metadata to be rendered in the `OPTIONS` response. Cache the result.
114
114
  def get_options_metadata
115
- return @_base_options_metadata ||= {
115
+ return {
116
116
  title: self.get_title,
117
117
  description: self.description,
118
118
  renders: [
@@ -285,9 +285,9 @@ module RESTFramework::BaseModelControllerMixin
285
285
  }.to_h
286
286
  end
287
287
 
288
- # Get a hash of metadata to be rendered in the `OPTIONS` response. Cache the result.
288
+ # Get a hash of metadata to be rendered in the `OPTIONS` response.
289
289
  def get_options_metadata
290
- return super().merge(
290
+ return super.merge(
291
291
  {
292
292
  fields: self.get_fields_metadata,
293
293
  callbacks: self._process_action_callbacks.as_json,
@@ -439,10 +439,13 @@ module RESTFramework::BaseModelControllerMixin
439
439
  return super || RESTFramework::NativeSerializer
440
440
  end
441
441
 
442
- # Get filtering backends, defaulting to using `ModelFilter` and `ModelOrderingFilter`.
442
+ # Get filtering backends, defaulting to using `ModelFilter`, `ModelOrderingFilter`, and
443
+ # `ModelSearchFilter`.
443
444
  def get_filter_backends
444
445
  return self.class.filter_backends || [
445
- RESTFramework::ModelFilter, RESTFramework::ModelOrderingFilter
446
+ RESTFramework::ModelFilter,
447
+ RESTFramework::ModelOrderingFilter,
448
+ RESTFramework::ModelSearchFilter,
446
449
  ]
447
450
  end
448
451
 
@@ -14,7 +14,7 @@ class RESTFramework::ModelFilter < RESTFramework::BaseFilter
14
14
  # Get a list of filterset fields for the current action. Fallback to columns because we don't want
15
15
  # to try filtering by any query parameter because that could clash with other query parameters.
16
16
  def _get_fields
17
- return @_get_fields ||= (
17
+ return (
18
18
  @controller.class.filterset_fields || @controller.get_fields(fallback: true)
19
19
  ).map(&:to_s)
20
20
  end
@@ -69,9 +69,7 @@ class RESTFramework::ModelOrderingFilter < RESTFramework::BaseFilter
69
69
  # Get a list of ordering fields for the current action. Do not fallback to columns in case the
70
70
  # user wants to order by a virtual column.
71
71
  def _get_fields
72
- return @_get_fields ||= (
73
- @controller.class.ordering_fields || @controller.get_fields
74
- )&.map(&:to_s)
72
+ return (@controller.class.ordering_fields || @controller.get_fields)&.map(&:to_s)
75
73
  end
76
74
 
77
75
  # Convert ordering string to an ordering configuration.
@@ -117,25 +115,43 @@ end
117
115
 
118
116
  # Multi-field text searching on models.
119
117
  class RESTFramework::ModelSearchFilter < RESTFramework::BaseFilter
120
- # Get a list of search fields for the current action. Fallback to columns because we need an
121
- # explicit list of columns to search on, so `nil` is useless in this context.
118
+ DEFAULT_SEARCH_COLUMNS = %w[name email title description note]
119
+
120
+ # Get a list of search fields for the current action. Fallback to columns but only grab a few
121
+ # common string-like columns by default.
122
122
  def _get_fields
123
- return @controller.class.search_fields || @controller.get_fields(fallback: true)
123
+ if search_fields = @controller.class.search_fields
124
+ return search_fields
125
+ end
126
+
127
+ columns = @controller.class.get_model.columns_hash.keys
128
+ return @controller.get_fields(fallback: true).select { |f|
129
+ f.in?(DEFAULT_SEARCH_COLUMNS) && f.in?(columns)
130
+ }
124
131
  end
125
132
 
126
133
  # Filter data according to the request query parameters.
127
134
  def get_filtered_data(data)
128
- fields = self._get_fields
129
135
  search = @controller.request.query_parameters[@controller.class.search_query_param]
130
136
 
131
- # Ensure we use array conditions to prevent SQL injection.
132
- if search.present? && !fields.empty?
133
- return data.where(
134
- fields.map { |f|
135
- "CAST(#{f} AS VARCHAR) #{@controller.class.search_ilike ? "ILIKE" : "LIKE"} ?"
136
- }.join(" OR "),
137
- *(["%#{search}%"] * fields.length),
138
- )
137
+ if search.present?
138
+ if fields = self._get_fields.presence
139
+ # MySQL doesn't support casting to VARCHAR, so we need to use CHAR instead.
140
+ data_type = if data.connection.adapter_name =~ /mysql/i
141
+ "CHAR"
142
+ else
143
+ # Sufficient for both PostgreSQL and SQLite.
144
+ "VARCHAR"
145
+ end
146
+
147
+ # Ensure we pass user input as arguments to prevent SQL injection.
148
+ return data.where(
149
+ fields.map { |f|
150
+ "CAST(#{f} AS #{data_type}) #{@controller.class.search_ilike ? "ILIKE" : "LIKE"} ?"
151
+ }.join(" OR "),
152
+ *(["%#{search}%"] * fields.length),
153
+ )
154
+ end
139
155
  end
140
156
 
141
157
  return data
@@ -1,6 +1,5 @@
1
1
  module RESTFramework::Utils
2
2
  HTTP_METHOD_ORDERING = %w(GET POST PUT PATCH DELETE OPTIONS HEAD)
3
- LABEL_FIELDS = %w(name label login title email username url)
4
3
 
5
4
  # Convert `extra_actions` hash to a consistent format: `{path:, methods:, kwargs:}`, and
6
5
  # additional metadata fields.
@@ -186,14 +185,15 @@ module RESTFramework::Utils
186
185
  def self.sub_fields_for(ref)
187
186
  if !ref.polymorphic? && model = ref.klass
188
187
  sub_fields = [model.primary_key].flatten.compact
188
+ label_fields = RESTFramework.config.label_fields
189
189
 
190
190
  # Preferrably find a database column to use as label.
191
- if match = LABEL_FIELDS.find { |f| f.in?(model.column_names) }
191
+ if match = label_fields.find { |f| f.in?(model.column_names) }
192
192
  return sub_fields + [match]
193
193
  end
194
194
 
195
195
  # Otherwise, find a method.
196
- if match = LABEL_FIELDS.find { |f| model.method_defined?(f) }
196
+ if match = label_fields.find { |f| model.method_defined?(f) }
197
197
  return sub_fields + [match]
198
198
  end
199
199
 
@@ -26,6 +26,8 @@ module RESTFramework
26
26
  ActiveStorage::Attachment
27
27
  ActiveStorage::Blob
28
28
  ).freeze
29
+ DEFAULT_LABEL_FIELDS = %w(name label login title email username url).freeze
30
+ DEFAULT_SEARCH_COLUMNS = DEFAULT_LABEL_FIELDS + %w(description note).freeze
29
31
 
30
32
  # Do not run `rrf_finalize` on controllers automatically using a `TracePoint` hook. This is a
31
33
  # performance option and must be global because we have to determine this before any
@@ -47,15 +49,23 @@ module RESTFramework
47
49
  # Whether the backtrace should be shown in rescued errors.
48
50
  attr_accessor :show_backtrace
49
51
 
50
- # Option to disable `rescue_from` on the controller mixins.
52
+ # Disable `rescue_from` on the controller mixins.
51
53
  attr_accessor :disable_rescue_from
52
54
 
53
- # Options to exclude certain classes from being added by default as association fields.
55
+ # Exclude certain classes from being added by default as association fields.
54
56
  attr_accessor :exclude_association_classes
55
57
 
58
+ # The default label fields to use when generating labels for `has_many` associations.
59
+ attr_accessor :label_fields
60
+
61
+ # The default search columns to use when generating search filters.
62
+ attr_accessor :search_columns
63
+
56
64
  def initialize
57
65
  self.show_backtrace = Rails.env.development?
58
66
  self.exclude_association_classes = DEFAULT_EXCLUDE_ASSOCIATION_CLASSES
67
+ self.label_fields = DEFAULT_LABEL_FIELDS
68
+ self.search_columns = DEFAULT_SEARCH_COLUMNS
59
69
  end
60
70
  end
61
71
 
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.8.3
4
+ version: 0.8.5
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: 2023-01-25 00:00:00.000000000 Z
11
+ date: 2023-02-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails