rails_admin 2.2.1 → 2.3.0

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: 1f7dc936446d63b1ecdd6f711d291c25d8c69222a8b4694edc248c9930e67983
4
- data.tar.gz: 4e89905db9dac3f8e7336e025fea98a7e03a5bd3d085406fa043f452999efc3c
3
+ metadata.gz: af37bc66a94584a21b754a3832c8556484f2e8fef1b5b926e373b11ae9ca7ff9
4
+ data.tar.gz: a50edd522f580bfcad1d574f5142c678c238acf9c56f269194cbd89b1255cec4
5
5
  SHA512:
6
- metadata.gz: 71faff5dae3bc4102bc8fb00a4deb42c601e49c5295fb17f88444f57409ab346d466dfd02b66a8998d9394cc270a6760c3586bc8fd7df83d4a6e078b830f93ce
7
- data.tar.gz: fc96ebe4813b3621dea3eadde119932e7fd4ebcca303100cbdfc3172c7d8fa2f15f93c489554b581bcd2fe10ab4f636a5927536d94081eb75ae4f867a34b3d17
6
+ metadata.gz: fa60983825f2d6ebca5c5b940a9e849cc491bb51981b1ee07a7b90cf6038167e4ac31823b49b7b0256d0f293aedaacf6cb0248e1ce038e7a03b72f5c3d70d8f9
7
+ data.tar.gz: 95f6c62c0d8dafe6ff4359992df55b1dde343fdf8a6ff4222d07f263d94cf1ddf1710a576ff83c6fb2cb25cac3edb566134dea9374f4b55a5e61ad673d93f47b
@@ -119,6 +119,13 @@
119
119
  .prop('name', operator_name)
120
120
  .append('<option value="_discard">...</option>')
121
121
  .append($('<option data-additional-fieldset="additional-fieldset" value="like"></option>').prop('selected', field_operator == "like").text(RailsAdmin.I18n.t("contains")))
122
+ .append(
123
+ $(
124
+ '<option data-additional-fieldset="additional-fieldset" value="not_like"></option>'
125
+ )
126
+ .prop("selected", field_operator == "not_like")
127
+ .text(RailsAdmin.I18n.t("does_not_contain"))
128
+ )
122
129
  .append($('<option data-additional-fieldset="additional-fieldset" value="is"></option>').prop('selected', field_operator == "is").text(RailsAdmin.I18n.t("is_exactly")))
123
130
  .append($('<option data-additional-fieldset="additional-fieldset" value="starts_with"></option>').prop('selected', field_operator == "starts_with").text(RailsAdmin.I18n.t("starts_with")))
124
131
  .append($('<option data-additional-fieldset="additional-fieldset" value="ends_with"></option>').prop('selected', field_operator == "ends_with").text(RailsAdmin.I18n.t("ends_with")))
@@ -103,7 +103,7 @@
103
103
  %td.other.left= link_to "...", @other_left_link, class: 'pjax'
104
104
  - properties.map{ |property| property.bind(:object, object) }.each do |property|
105
105
  - value = property.pretty_value
106
- %td{class: "#{property.css_class} #{property.type_css_class}", title: strip_tags(value.to_s)}= value
106
+ %td{class: "#{property.css_class} #{property.type_css_class}", title: value}= value
107
107
  - if @other_right_link ||= other_right && index_path(params.merge(set: (params[:set].to_i + 1)))
108
108
  %td.other.right= link_to "...", @other_right_link, class: 'pjax'
109
109
  - unless frozen_columns
@@ -13,6 +13,7 @@ en:
13
13
  last_week: Last week
14
14
  number: Number ...
15
15
  contains: Contains
16
+ does_not_contain: Does not contain
16
17
  is_exactly: Is exactly
17
18
  starts_with: Starts with
18
19
  ends_with: Ends with
@@ -250,7 +250,7 @@ module RailsAdmin
250
250
 
251
251
  @value = begin
252
252
  case @operator
253
- when 'default', 'like'
253
+ when 'default', 'like', 'not_like'
254
254
  "%#{@value}%"
255
255
  when 'starts_with'
256
256
  "#{@value}%"
@@ -262,7 +262,13 @@ module RailsAdmin
262
262
  end
263
263
 
264
264
  if ['postgresql', 'postgis'].include? ar_adapter
265
- ["(#{@column} ILIKE ?)", @value]
265
+ if @operator == 'not_like'
266
+ ["(#{@column} NOT ILIKE ?)", @value]
267
+ else
268
+ ["(#{@column} ILIKE ?)", @value]
269
+ end
270
+ elsif @operator == 'not_like'
271
+ ["(LOWER(#{@column}) NOT LIKE ?)", @value]
266
272
  else
267
273
  ["(LOWER(#{@column}) LIKE ?)", @value]
268
274
  end
@@ -0,0 +1,348 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record'
4
+ require 'rails_admin/adapters/active_record/association'
5
+ require 'rails_admin/adapters/active_record/model_extension'
6
+ require 'rails_admin/adapters/active_record/property'
7
+
8
+ module RailsAdmin
9
+ module Adapters
10
+ module ActiveRecord
11
+ DISABLED_COLUMN_TYPES = %i[tsvector blob binary spatial hstore geometry].freeze
12
+
13
+ def model_with_extension
14
+ @model_with_extension ||=
15
+ begin
16
+ klass = Class.new(model) do
17
+ include ModelExtension
18
+ end
19
+ klass.instance_eval <<-RUBY, __FILE__, __LINE__+1
20
+ def name
21
+ "#{@model_name.to_s}"
22
+ end
23
+ RUBY
24
+ klass
25
+ end
26
+ end
27
+
28
+ def new(params = {})
29
+ model_with_extension.new(params)
30
+ end
31
+
32
+ def get(id, scope = nil)
33
+ scope = model_with_extension.merge(scope || scoped)
34
+ scope.where(primary_key => id).first
35
+ end
36
+
37
+ def scoped
38
+ model_with_extension.all
39
+ end
40
+
41
+ def first(options = {}, scope = nil)
42
+ all(options, scope).first
43
+ end
44
+
45
+ def all(options = {}, scope = nil)
46
+ scope = model_with_extension.merge(scope || scoped)
47
+ scope = scope.includes(options[:include]) if options[:include]
48
+ scope = scope.limit(options[:limit]) if options[:limit]
49
+ scope = bulk_scope(scope, options) if options[:bulk_ids]
50
+ scope = query_scope(scope, options[:query]) if options[:query]
51
+ scope = filter_scope(scope, options[:filters]) if options[:filters]
52
+ scope = scope.send(Kaminari.config.page_method_name, options[:page]).per(options[:per]) if options[:page] && options[:per]
53
+ scope = sort_scope(scope, options) if options[:sort]
54
+ scope
55
+ end
56
+
57
+ def count(options = {}, scope = nil)
58
+ all(options.merge(limit: false, page: false), scope).count(:all)
59
+ end
60
+
61
+ def destroy(objects)
62
+ Array.wrap(objects).each(&:destroy)
63
+ end
64
+
65
+ def associations
66
+ model.reflect_on_all_associations.collect do |association|
67
+ Association.new(association, model)
68
+ end
69
+ end
70
+
71
+ def properties
72
+ columns = model.columns.reject do |c|
73
+ c.type.blank? ||
74
+ DISABLED_COLUMN_TYPES.include?(c.type.to_sym) ||
75
+ c.try(:array)
76
+ end
77
+ columns.collect do |property|
78
+ Property.new(property, model)
79
+ end
80
+ end
81
+
82
+ def base_class
83
+ model.base_class
84
+ end
85
+
86
+ delegate :primary_key, :table_name, to: :model, prefix: false
87
+
88
+ def quoted_table_name
89
+ model.quoted_table_name
90
+ end
91
+
92
+ def quote_column_name(name)
93
+ model.connection.quote_column_name(name)
94
+ end
95
+
96
+ def encoding
97
+ adapter =
98
+ if ::ActiveRecord::Base.respond_to?(:connection_db_config)
99
+ ::ActiveRecord::Base.connection_db_config.configuration_hash[:adapter]
100
+ else
101
+ ::ActiveRecord::Base.connection_config[:adapter]
102
+ end
103
+ case adapter
104
+ when 'postgresql'
105
+ ::ActiveRecord::Base.connection.select_one("SELECT ''::text AS str;").values.first.encoding
106
+ when 'mysql2'
107
+ if RUBY_ENGINE == 'jruby'
108
+ ::ActiveRecord::Base.connection.select_one("SELECT '' AS str;").values.first.encoding
109
+ else
110
+ ::ActiveRecord::Base.connection.raw_connection.encoding
111
+ end
112
+ when 'oracle_enhanced'
113
+ ::ActiveRecord::Base.connection.select_one('SELECT dummy FROM DUAL').values.first.encoding
114
+ else
115
+ ::ActiveRecord::Base.connection.select_one("SELECT '' AS str;").values.first.encoding
116
+ end
117
+ end
118
+
119
+ def embedded?
120
+ false
121
+ end
122
+
123
+ def cyclic?
124
+ false
125
+ end
126
+
127
+ def adapter_supports_joins?
128
+ true
129
+ end
130
+
131
+ private
132
+
133
+ def bulk_scope(scope, options)
134
+ scope.where(primary_key => options[:bulk_ids])
135
+ end
136
+
137
+ def sort_scope(scope, options)
138
+ direction = options[:sort_reverse] ? :asc : :desc
139
+ case options[:sort]
140
+ when String, Symbol
141
+ scope.reorder("#{options[:sort]} #{direction}")
142
+ when Array
143
+ scope.reorder(options[:sort].zip(Array.new(options[:sort].size) { direction }).to_h)
144
+ when Hash
145
+ scope.reorder(options[:sort].map { |table_name, column| "#{table_name}.#{column}" }.
146
+ zip(Array.new(options[:sort].size) { direction }).to_h)
147
+ else
148
+ raise ArgumentError.new("Unsupported sort value: #{options[:sort]}")
149
+ end
150
+ end
151
+
152
+ class WhereBuilder
153
+ def initialize(scope)
154
+ @statements = []
155
+ @values = []
156
+ @tables = []
157
+ @scope = scope
158
+ end
159
+
160
+ def add(field, value, operator)
161
+ field.searchable_columns.flatten.each do |column_infos|
162
+ statement, value1, value2 = StatementBuilder.new(column_infos[:column], column_infos[:type], value, operator, @scope.connection.adapter_name).to_statement
163
+ @statements << statement if statement.present?
164
+ @values << value1 unless value1.nil?
165
+ @values << value2 unless value2.nil?
166
+ table, column = column_infos[:column].split('.')
167
+ @tables.push(table) if column
168
+ end
169
+ end
170
+
171
+ def build
172
+ scope = @scope.where(@statements.join(' OR '), *@values)
173
+ scope = scope.references(*@tables.uniq) if @tables.any?
174
+ scope
175
+ end
176
+ end
177
+
178
+ def query_scope(scope, query, fields = config.list.fields.select(&:queryable?))
179
+ if config.list.search_by
180
+ scope.send(config.list.search_by, query)
181
+ else
182
+ wb = WhereBuilder.new(scope)
183
+ fields.each do |field|
184
+ value = parse_field_value(field, query)
185
+ wb.add(field, value, field.search_operator)
186
+ end
187
+ # OR all query statements
188
+ wb.build
189
+ end
190
+ end
191
+
192
+ # filters example => {"string_field"=>{"0055"=>{"o"=>"like", "v"=>"test_value"}}, ...}
193
+ # "0055" is the filter index, no use here. o is the operator, v the value
194
+ def filter_scope(scope, filters, fields = config.list.fields.select(&:filterable?))
195
+ filters.each_pair do |field_name, filters_dump|
196
+ filters_dump.each_value do |filter_dump|
197
+ wb = WhereBuilder.new(scope)
198
+ field = fields.detect { |f| f.name.to_s == field_name }
199
+ value = parse_field_value(field, filter_dump[:v])
200
+
201
+ wb.add(field, value, (filter_dump[:o] || RailsAdmin::Config.default_search_operator))
202
+ # AND current filter statements to other filter statements
203
+ scope = wb.build
204
+ end
205
+ end
206
+ scope
207
+ end
208
+
209
+ def build_statement(column, type, value, operator)
210
+ StatementBuilder.new(column, type, value, operator, model.connection.adapter_name).to_statement
211
+ end
212
+
213
+ class StatementBuilder < RailsAdmin::AbstractModel::StatementBuilder
214
+ def initialize(column, type, value, operator, adapter_name)
215
+ super column, type, value, operator
216
+ @adapter_name = adapter_name
217
+ end
218
+
219
+ protected
220
+
221
+ def unary_operators
222
+ case @type
223
+ when :boolean
224
+ boolean_unary_operators
225
+ when :uuid
226
+ uuid_unary_operators
227
+ when :integer, :decimal, :float
228
+ numeric_unary_operators
229
+ else
230
+ generic_unary_operators
231
+ end
232
+ end
233
+
234
+ private
235
+
236
+ def generic_unary_operators
237
+ {
238
+ '_blank' => ["(#{@column} IS NULL OR #{@column} = '')"],
239
+ '_present' => ["(#{@column} IS NOT NULL AND #{@column} != '')"],
240
+ '_null' => ["(#{@column} IS NULL)"],
241
+ '_not_null' => ["(#{@column} IS NOT NULL)"],
242
+ '_empty' => ["(#{@column} = '')"],
243
+ '_not_empty' => ["(#{@column} != '')"],
244
+ }
245
+ end
246
+
247
+ def boolean_unary_operators
248
+ generic_unary_operators.merge(
249
+ '_blank' => ["(#{@column} IS NULL)"],
250
+ '_empty' => ["(#{@column} IS NULL)"],
251
+ '_present' => ["(#{@column} IS NOT NULL)"],
252
+ '_not_empty' => ["(#{@column} IS NOT NULL)"],
253
+ )
254
+ end
255
+ alias_method :numeric_unary_operators, :boolean_unary_operators
256
+ alias_method :uuid_unary_operators, :boolean_unary_operators
257
+
258
+ def range_filter(min, max)
259
+ if min && max && min == max
260
+ ["(#{@column} = ?)", min]
261
+ elsif min && max
262
+ ["(#{@column} BETWEEN ? AND ?)", min, max]
263
+ elsif min
264
+ ["(#{@column} >= ?)", min]
265
+ elsif max
266
+ ["(#{@column} <= ?)", max]
267
+ end
268
+ end
269
+
270
+ def build_statement_for_type
271
+ case @type
272
+ when :boolean then build_statement_for_boolean
273
+ when :integer, :decimal, :float then build_statement_for_integer_decimal_or_float
274
+ when :string, :text, :citext then build_statement_for_string_or_text
275
+ when :enum then build_statement_for_enum
276
+ when :belongs_to_association then build_statement_for_belongs_to_association
277
+ when :uuid then build_statement_for_uuid
278
+ end
279
+ end
280
+
281
+ def build_statement_for_boolean
282
+ case @value
283
+ when 'false', 'f', '0'
284
+ ["(#{@column} IS NULL OR #{@column} = ?)", false]
285
+ when 'true', 't', '1'
286
+ ["(#{@column} = ?)", true]
287
+ end
288
+ end
289
+
290
+ def column_for_value(value)
291
+ ["(#{@column} = ?)", value]
292
+ end
293
+
294
+ def build_statement_for_belongs_to_association
295
+ return if @value.blank?
296
+
297
+ ["(#{@column} = ?)", @value.to_i] if @value.to_i.to_s == @value
298
+ end
299
+
300
+ def build_statement_for_string_or_text
301
+ return if @value.blank?
302
+
303
+ return ["(#{@column} = ?)", @value] if ['is', '='].include?(@operator)
304
+
305
+ @value = @value.mb_chars.downcase unless %w[postgresql postgis].include? ar_adapter
306
+
307
+ @value =
308
+ case @operator
309
+ when 'default', 'like', 'not_like'
310
+ "%#{@value}%"
311
+ when 'starts_with'
312
+ "#{@value}%"
313
+ when 'ends_with'
314
+ "%#{@value}"
315
+ else
316
+ return
317
+ end
318
+
319
+ if %w[postgresql postgis].include? ar_adapter
320
+ if @operator == 'not_like'
321
+ ["(#{@column} NOT ILIKE ?)", @value]
322
+ else
323
+ ["(#{@column} ILIKE ?)", @value]
324
+ end
325
+ elsif @operator == 'not_like'
326
+ ["(LOWER(#{@column}) NOT LIKE ?)", @value]
327
+ else
328
+ ["(LOWER(#{@column}) LIKE ?)", @value]
329
+ end
330
+ end
331
+
332
+ def build_statement_for_enum
333
+ return if @value.blank?
334
+
335
+ ["(#{@column} IN (?))", Array.wrap(@value)]
336
+ end
337
+
338
+ def build_statement_for_uuid
339
+ column_for_value(@value) if /\A[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}\z/.match?(@value.to_s)
340
+ end
341
+
342
+ def ar_adapter
343
+ @adapter_name.downcase
344
+ end
345
+ end
346
+ end
347
+ end
348
+ end
@@ -258,6 +258,8 @@ module RailsAdmin
258
258
  return if @value.blank?
259
259
  @value = begin
260
260
  case @operator
261
+ when 'not_like'
262
+ Regexp.compile("^((?!#{Regexp.escape(@value)}).)*$", Regexp::IGNORECASE)
261
263
  when 'default', 'like'
262
264
  Regexp.compile(Regexp.escape(@value), Regexp::IGNORECASE)
263
265
  when 'starts_with'
@@ -198,7 +198,7 @@ module RailsAdmin
198
198
  end
199
199
 
200
200
  def default_search_operator=(operator)
201
- if %w(default like starts_with ends_with is =).include? operator
201
+ if %w(default like not_like starts_with ends_with is =).include? operator
202
202
  @default_search_operator = operator
203
203
  else
204
204
  raise(ArgumentError.new("Search operator '#{operator}' not supported"))
@@ -1,8 +1,8 @@
1
1
  module RailsAdmin
2
2
  class Version
3
3
  MAJOR = 2
4
- MINOR = 2
5
- PATCH = 1
4
+ MINOR = 3
5
+ PATCH = 0
6
6
  PRE = nil
7
7
 
8
8
  class << self
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_admin
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erik Michaels-Ober
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2021-08-08 00:00:00.000000000 Z
15
+ date: 2024-07-06 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: builder
@@ -348,6 +348,7 @@ files:
348
348
  - lib/rails_admin.rb
349
349
  - lib/rails_admin/abstract_model.rb
350
350
  - lib/rails_admin/adapters/active_record.rb
351
+ - lib/rails_admin/adapters/active_record.rb.bak
351
352
  - lib/rails_admin/adapters/active_record/abstract_object.rb
352
353
  - lib/rails_admin/adapters/active_record/association.rb
353
354
  - lib/rails_admin/adapters/active_record/property.rb
@@ -614,7 +615,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
614
615
  - !ruby/object:Gem::Version
615
616
  version: 1.8.11
616
617
  requirements: []
617
- rubygems_version: 3.1.4
618
+ rubygems_version: 3.2.33
618
619
  signing_key:
619
620
  specification_version: 4
620
621
  summary: Admin for Rails