diff --git a/app/assets/javascripts/rails_admin/ra.filter-box.js b/app/assets/javascripts/rails_admin/ra.filter-box.js
index ba2f41e8cf..522b9249f7 100644
--- a/app/assets/javascripts/rails_admin/ra.filter-box.js
+++ b/app/assets/javascripts/rails_admin/ra.filter-box.js
@@ -164,6 +164,7 @@
)
);
break;
+ case "citext":
case "string":
case "text":
case "belongs_to_association":
@@ -180,6 +181,13 @@
.prop("selected", field_operator == "like")
.text(RailsAdmin.I18n.t("contains"))
)
+ .append(
+ $(
+ ''
+ )
+ .prop("selected", field_operator == "not_like")
+ .text(RailsAdmin.I18n.t("does_not_contain"))
+ )
.append(
$(
''
diff --git a/app/assets/javascripts/rails_admin/ra.nested-form-hooks.js b/app/assets/javascripts/rails_admin/ra.nested-form-hooks.js
index cee6ef82ad..4b87880b14 100644
--- a/app/assets/javascripts/rails_admin/ra.nested-form-hooks.js
+++ b/app/assets/javascripts/rails_admin/ra.nested-form-hooks.js
@@ -30,7 +30,10 @@
content = parent_group.children(".tab-content");
toggler = controls.find(".toggler");
nav.append(new_tab);
- $(window.document).trigger("rails_admin.dom_ready", [field, parent_group]);
+
+ const event = new CustomEvent("rails_admin.dom_ready", { detail: field });
+ document.dispatchEvent(event);
+
new_tab.children("a").tab("show");
if (!one_to_one) {
nav.filter(":hidden").show("slow");
diff --git a/app/assets/javascripts/rails_admin/ra.remote-form.js b/app/assets/javascripts/rails_admin/ra.remote-form.js
index f416c812e5..df262132bf 100644
--- a/app/assets/javascripts/rails_admin/ra.remote-form.js
+++ b/app/assets/javascripts/rails_admin/ra.remote-form.js
@@ -105,7 +105,8 @@
})
.html(saveButtonText);
- $(document).trigger("rails_admin.dom_ready", [form]);
+ const event = new CustomEvent("rails_admin.dom_ready", { detail: form });
+ document.dispatchEvent(event);
form.bind("ajax:complete", function (event) {
var data = event.detail[0],
diff --git a/app/assets/javascripts/rails_admin/ra.sidescroll.js b/app/assets/javascripts/rails_admin/ra.sidescroll.js
index 4987845eb6..389608eb1e 100644
--- a/app/assets/javascripts/rails_admin/ra.sidescroll.js
+++ b/app/assets/javascripts/rails_admin/ra.sidescroll.js
@@ -1,7 +1,7 @@
(function ($) {
"use strict";
- $(document).on("rails_admin.dom_ready", () => {
+ document.addEventListener("rails_admin.dom_ready", () => {
const listForm = document.getElementById("bulk_form");
if (!listForm || !listForm.classList.contains("ra-sidescroll")) {
return;
diff --git a/app/assets/javascripts/rails_admin/ra.widgets.js b/app/assets/javascripts/rails_admin/ra.widgets.js
index dc8d571cc2..ca159be345 100644
--- a/app/assets/javascripts/rails_admin/ra.widgets.js
+++ b/app/assets/javascripts/rails_admin/ra.widgets.js
@@ -1,5 +1,5 @@
(function ($) {
- $(document).on("rails_admin.dom_ready", function (e, content) {
+ document.addEventListener("rails_admin.dom_ready", function (event) {
var $editors,
array,
config_options,
@@ -9,7 +9,7 @@
goFroalaWysiwygs,
goSimpleMDEs,
options;
- content = content ? content : $("form");
+ var content = event.detail || $("form");
if (content.length) {
$.fn.datetimepicker.defaults.icons = {
time: "fa fa-clock-o",
diff --git a/app/assets/javascripts/rails_admin/ui.js b/app/assets/javascripts/rails_admin/ui.js
index 729c7a9b6f..b39d25ef53 100644
--- a/app/assets/javascripts/rails_admin/ui.js
+++ b/app/assets/javascripts/rails_admin/ui.js
@@ -94,14 +94,17 @@
$("html").attr("lang"),
$("#admin-js").data("i18nOptions")
);
- $(document).trigger("rails_admin.dom_ready");
+
+ const event = new CustomEvent("rails_admin.dom_ready");
+ document.dispatchEvent(event);
});
$(document).on("pjax:end", function () {
- $(document).trigger("rails_admin.dom_ready");
+ const event = new CustomEvent("rails_admin.dom_ready");
+ document.dispatchEvent(event);
});
- $(document).on("rails_admin.dom_ready", function () {
+ document.addEventListener("rails_admin.dom_ready", function () {
$(".nav.nav-pills li.active").removeClass("active");
$(
'.nav.nav-pills li[data-model="' + $(".page-header").data("model") + '"]'
diff --git a/config/locales/rails_admin.en.yml b/config/locales/rails_admin.en.yml
index fb395e5b10..0e34dfb9d2 100644
--- a/config/locales/rails_admin.en.yml
+++ b/config/locales/rails_admin.en.yml
@@ -14,6 +14,7 @@ en:
time: Time ...
number: Number ...
contains: Contains
+ does_not_contain: Does not contain
is_exactly: Is exactly
starts_with: Starts with
ends_with: Ends with
diff --git a/lib/rails_admin/adapters/active_record.rb b/lib/rails_admin/adapters/active_record.rb
index 244c762c10..5f6b4b4271 100644
--- a/lib/rails_admin/adapters/active_record.rb
+++ b/lib/rails_admin/adapters/active_record.rb
@@ -220,7 +220,7 @@ def build_statement_for_type
case @type
when :boolean then build_statement_for_boolean
when :integer, :decimal, :float then build_statement_for_integer_decimal_or_float
- when :string, :text then build_statement_for_string_or_text
+ when :string, :text, :citext then build_statement_for_string_or_text
when :enum then build_statement_for_enum
when :belongs_to_association then build_statement_for_belongs_to_association
when :uuid then build_statement_for_uuid
@@ -252,7 +252,7 @@ def build_statement_for_string_or_text
@value = begin
case @operator
- when 'default', 'like'
+ when 'default', 'like', 'not_like'
"%#{@value}%"
when 'starts_with'
"#{@value}%"
@@ -264,7 +264,13 @@ def build_statement_for_string_or_text
end
if ['postgresql', 'postgis'].include? ar_adapter
- ["(#{@column} ILIKE ?)", @value]
+ if @operator == 'not_like'
+ ["(#{@column} NOT ILIKE ?)", @value]
+ else
+ ["(#{@column} ILIKE ?)", @value]
+ end
+ elsif @operator == 'not_like'
+ ["(LOWER(#{@column}) NOT LIKE ?)", @value]
else
["(LOWER(#{@column}) LIKE ?)", @value]
end
diff --git a/lib/rails_admin/adapters/mongoid.rb b/lib/rails_admin/adapters/mongoid.rb
index d9f1123362..310f90a7b2 100644
--- a/lib/rails_admin/adapters/mongoid.rb
+++ b/lib/rails_admin/adapters/mongoid.rb
@@ -260,6 +260,8 @@ def build_statement_for_string_or_text
return if @value.blank?
@value = begin
case @operator
+ when 'not_like'
+ Regexp.compile("^((?!#{Regexp.escape(@value)}).)*$", Regexp::IGNORECASE)
when 'default', 'like'
Regexp.compile(Regexp.escape(@value), Regexp::IGNORECASE)
when 'starts_with'
diff --git a/lib/rails_admin/config.rb b/lib/rails_admin/config.rb
index 3bbf399665..8d47d814b1 100644
--- a/lib/rails_admin/config.rb
+++ b/lib/rails_admin/config.rb
@@ -218,7 +218,7 @@ def current_user_method(&block)
end
def default_search_operator=(operator)
- if %w(default like starts_with ends_with is =).include? operator
+ if %w(default like not_like starts_with ends_with is =).include? operator
@default_search_operator = operator
else
raise(ArgumentError.new("Search operator '#{operator}' not supported"))
diff --git a/lib/rails_admin/config/fields/types/all.rb b/lib/rails_admin/config/fields/types/all.rb
index 9ba58c66e8..f431256689 100644
--- a/lib/rails_admin/config/fields/types/all.rb
+++ b/lib/rails_admin/config/fields/types/all.rb
@@ -38,3 +38,4 @@
require 'rails_admin/config/fields/types/json'
require 'rails_admin/config/fields/types/inet'
require 'rails_admin/config/fields/types/uuid'
+require 'rails_admin/config/fields/types/citext'
diff --git a/lib/rails_admin/config/fields/types/citext.rb b/lib/rails_admin/config/fields/types/citext.rb
new file mode 100644
index 0000000000..c37de29c3e
--- /dev/null
+++ b/lib/rails_admin/config/fields/types/citext.rb
@@ -0,0 +1,13 @@
+require 'rails_admin/config/fields/types/text'
+
+module RailsAdmin
+ module Config
+ module Fields
+ module Types
+ class Citext < Text
+ RailsAdmin::Config::Fields::Types.register(:citext, self)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/rails_admin/adapters/active_record_spec.rb b/spec/rails_admin/adapters/active_record_spec.rb
index 715deba334..45fef2376b 100644
--- a/spec/rails_admin/adapters/active_record_spec.rb
+++ b/spec/rails_admin/adapters/active_record_spec.rb
@@ -16,6 +16,13 @@
'(LOWER(field) LIKE ?)'
end
end
+ let(:not_like) do
+ if ['postgresql', 'postgis'].include? activerecord_config[:adapter]
+ '(field NOT ILIKE ?)'
+ else
+ '(LOWER(field) NOT LIKE ?)'
+ end
+ end
def predicates_for(scope)
scope.where_clause.instance_variable_get(:@predicates)
@@ -274,6 +281,7 @@ def build_statement(type, value, operator)
expect(build_statement(:string, 'foo', 'was')).to be_nil
expect(build_statement(:string, 'foo', 'default')).to eq([like, '%foo%'])
expect(build_statement(:string, 'foo', 'like')).to eq([like, '%foo%'])
+ expect(build_statement(:string, 'foo', 'not_like')).to eq([not_like, '%foo%'])
expect(build_statement(:string, 'foo', 'starts_with')).to eq([like, 'foo%'])
expect(build_statement(:string, 'foo', 'ends_with')).to eq([like, '%foo'])
expect(build_statement(:string, 'foo', 'is')).to eq(['(field = ?)', 'foo'])
diff --git a/spec/rails_admin/adapters/mongoid_spec.rb b/spec/rails_admin/adapters/mongoid_spec.rb
index a3e09f794b..0cce88a049 100644
--- a/spec/rails_admin/adapters/mongoid_spec.rb
+++ b/spec/rails_admin/adapters/mongoid_spec.rb
@@ -385,6 +385,7 @@ def parse_value(value)
expect(@abstract_model.send(:build_statement, :field, :string, 'foo', 'was')).to be_nil
expect(@abstract_model.send(:build_statement, :field, :string, 'foo', 'default')).to eq(field: /foo/i)
expect(@abstract_model.send(:build_statement, :field, :string, 'foo', 'like')).to eq(field: /foo/i)
+ expect(@abstract_model.send(:build_statement, :field, :string, 'foo', 'not_like')).to eq(field: /^((?!foo).)*$/i)
expect(@abstract_model.send(:build_statement, :field, :string, 'foo', 'starts_with')).to eq(field: /^foo/i)
expect(@abstract_model.send(:build_statement, :field, :string, 'foo', 'ends_with')).to eq(field: /foo$/i)
expect(@abstract_model.send(:build_statement, :field, :string, 'foo', 'is')).to eq(field: 'foo')
diff --git a/spec/rails_admin/config/fields/types/citext_spec.rb b/spec/rails_admin/config/fields/types/citext_spec.rb
new file mode 100644
index 0000000000..d9d05270a9
--- /dev/null
+++ b/spec/rails_admin/config/fields/types/citext_spec.rb
@@ -0,0 +1,7 @@
+require 'spec_helper'
+
+RSpec.describe RailsAdmin::Config::Fields::Types::Citext do
+ it_behaves_like 'a generic field type', :string_field
+
+ it_behaves_like 'a string-like field type', :string_field
+end