rails_admin 2.2.0 → 2.3.0

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: 36b3499d6b3b8d89882cdd41af33bfd4e4bf97fd5b035664ea73fda237a62279
4
- data.tar.gz: d842cfc1f2dd3ec81aebf7c0fcf49111a0945d27abf07465fdf80c178e21d410
3
+ metadata.gz: af37bc66a94584a21b754a3832c8556484f2e8fef1b5b926e373b11ae9ca7ff9
4
+ data.tar.gz: a50edd522f580bfcad1d574f5142c678c238acf9c56f269194cbd89b1255cec4
5
5
  SHA512:
6
- metadata.gz: 97579593acfe1b195682a979192b5c7436ba46ab4ba63c33ae9d4fa1f682b45f6a09fb80ecb66cc7aaac6ff23c744cbb2bf86b6ee22dc82e01d17a6edde24cd6
7
- data.tar.gz: 2d4980005f040bebcccce89d734ed296d01ad0573b74dd8264af8cf308ff4b948766de73071c4762639058d40cefefbb33022f0acc1c650ad55aea70e314538f
6
+ metadata.gz: fa60983825f2d6ebca5c5b940a9e849cc491bb51981b1ee07a7b90cf6038167e4ac31823b49b7b0256d0f293aedaacf6cb0248e1ce038e7a03b72f5c3d70d8f9
7
+ data.tar.gz: 95f6c62c0d8dafe6ff4359992df55b1dde343fdf8a6ff4222d07f263d94cf1ddf1710a576ff83c6fb2cb25cac3edb566134dea9374f4b55a5e61ad673d93f47b
@@ -98,6 +98,7 @@
98
98
  $('<option value="_blank"></option>').prop('selected', field_value == "_blank").text(RailsAdmin.I18n.t("is_blank")),
99
99
  '<option disabled="disabled">---------</option>'
100
100
  ])
101
+ .append(select_options)
101
102
  .add(
102
103
  $('<select multiple="multiple" class="select-multiple input-sm form-control"></select>')
103
104
  .css('display', multiple_values ? 'inline-block' : 'none')
@@ -118,6 +119,13 @@
118
119
  .prop('name', operator_name)
119
120
  .append('<option value="_discard">...</option>')
120
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
+ )
121
129
  .append($('<option data-additional-fieldset="additional-fieldset" value="is"></option>').prop('selected', field_operator == "is").text(RailsAdmin.I18n.t("is_exactly")))
122
130
  .append($('<option data-additional-fieldset="additional-fieldset" value="starts_with"></option>').prop('selected', field_operator == "starts_with").text(RailsAdmin.I18n.t("starts_with")))
123
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,7 +1,7 @@
1
1
  module RailsAdmin
2
2
  class Version
3
3
  MAJOR = 2
4
- MINOR = 2
4
+ MINOR = 3
5
5
  PATCH = 0
6
6
  PRE = nil
7
7
 
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.0
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erik Michaels-Ober
@@ -9,10 +9,10 @@ authors:
9
9
  - Petteri Kaapa
10
10
  - Benoit Benezech
11
11
  - Mitsuhiro Shibuya
12
- autorequire:
12
+ autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2021-07-24 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
@@ -599,7 +600,7 @@ homepage: https://github.com/sferik/rails_admin
599
600
  licenses:
600
601
  - MIT
601
602
  metadata: {}
602
- post_install_message:
603
+ post_install_message:
603
604
  rdoc_options: []
604
605
  require_paths:
605
606
  - lib
@@ -614,8 +615,8 @@ 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
- signing_key:
618
+ rubygems_version: 3.2.33
619
+ signing_key:
619
620
  specification_version: 4
620
621
  summary: Admin for Rails
621
622
  test_files: []