rest_framework 0.8.3 → 0.8.5

Sign up to get free protection for your applications and to get access to all the features.
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