diff --git a/Gemfile.lock b/Gemfile.lock index ef386b26c10..973bc7d69d5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - activeadmin (4.0.0.beta10) + activeadmin (4.0.0.beta11) arbre (~> 2.0) csv formtastic (>= 3.1) diff --git a/UPGRADING.md b/UPGRADING.md index b79e5d22191..f02530ac78d 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -8,14 +8,14 @@ ActiveAdmin v4 uses TailwindCSS. It has **mobile web, dark mode and RTL support* These instructions assume the `cssbundling-rails` and `importmap-rails` gems are already installed and you have run their install commands in your app. If you haven't done so, please do before continuing. -Update your `Gemfile` with `gem "activeadmin", "4.0.0.beta10"` and then run `gem install activeadmin --pre`. +Update your `Gemfile` with `gem "activeadmin", "4.0.0.beta11"` and then run `gem install activeadmin --pre`. Now, run `rails generate active_admin:assets` to replace the old assets with the new files. Then add the npm package and update the `build:css` script. ``` -yarn add @activeadmin/activeadmin@4.0.0-beta10 +yarn add @activeadmin/activeadmin@4.0.0-beta11 npm pkg set scripts.build:css="tailwindcss -i ./app/assets/stylesheets/active_admin.css -o ./app/assets/builds/active_admin.css --minify -c tailwind-active_admin.config.js" ``` diff --git a/app/controllers/active_admin/resource_controller/decorators.rb b/app/controllers/active_admin/resource_controller/decorators.rb index 59243364d66..a916cb24b12 100644 --- a/app/controllers/active_admin/resource_controller/decorators.rb +++ b/app/controllers/active_admin/resource_controller/decorators.rb @@ -61,7 +61,7 @@ def self.wrap(decorator) def self.wrap!(parent, name) ::Class.new parent do delegate :reorder, :page, :current_page, :total_pages, :limit_value, - :total_count, :total_pages, :offset, :to_key, :group_values, + :total_count, :offset, :to_key, :group_values, :except, :find_each, :ransack, to: :object define_singleton_method(:name) { name } diff --git a/app/helpers/active_admin/display_helper.rb b/app/helpers/active_admin/display_helper.rb index 60eef0ff783..f86172af422 100644 --- a/app/helpers/active_admin/display_helper.rb +++ b/app/helpers/active_admin/display_helper.rb @@ -108,8 +108,8 @@ def boolean_attr?(resource, attr, value) when TrueClass, FalseClass true else - if resource.class.respond_to? :columns_hash - column = resource.class.columns_hash[attr.to_s] and column.type == :boolean + if resource.class.respond_to? :attribute_types + resource.class.attribute_types[attr.to_s].is_a?(ActiveModel::Type::Boolean) end end end diff --git a/config/importmap.rb b/config/importmap.rb index e580fc67d87..d6555b8729a 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true pin "flowbite", preload: true # downloaded from https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.2.1/flowbite.min.js -pin "@rails/ujs", to: "rails_ujs_esm.js", preload: true # downloaded from https://cdn.jsdelivr.net/npm/@rails/ujs@7.1.2/+esm +pin "@rails/ujs", to: "rails_ujs_esm.js", preload: true # downloaded from https://cdn.jsdelivr.net/npm/@rails/ujs@7.1.400/+esm pin "active_admin", to: "active_admin.js", preload: true pin_all_from File.expand_path("../app/javascript/active_admin", __dir__), under: "active_admin", preload: true diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 092f323a9e8..ce47679e72e 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -275,4 +275,4 @@ DEPENDENCIES github-pages BUNDLED WITH - 2.5.15 + 2.5.17 diff --git a/gemfiles/rails_61/Gemfile.lock b/gemfiles/rails_61/Gemfile.lock index 5691de209a0..ff9d5c3a100 100644 --- a/gemfiles/rails_61/Gemfile.lock +++ b/gemfiles/rails_61/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: ../.. specs: - activeadmin (4.0.0.beta10) + activeadmin (4.0.0.beta11) arbre (~> 2.0) csv formtastic (>= 3.1) diff --git a/gemfiles/rails_70/Gemfile.lock b/gemfiles/rails_70/Gemfile.lock index 652de41a1fd..75e79a70223 100644 --- a/gemfiles/rails_70/Gemfile.lock +++ b/gemfiles/rails_70/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: ../.. specs: - activeadmin (4.0.0.beta10) + activeadmin (4.0.0.beta11) arbre (~> 2.0) csv formtastic (>= 3.1) diff --git a/gemfiles/rails_71/Gemfile b/gemfiles/rails_71/Gemfile index d7be2247f33..e6c5d5b5945 100644 --- a/gemfiles/rails_71/Gemfile +++ b/gemfiles/rails_71/Gemfile @@ -33,7 +33,7 @@ group :test do gem "launchy" gem "parallel_tests" gem "rspec-rails" - gem "sqlite3", "~> 1.7", platform: :mri # FIXME: relax this dependency when rails/rails#51636 will be released + gem "sqlite3", platform: :mri # Translations gem "i18n-tasks" diff --git a/gemfiles/rails_71/Gemfile.lock b/gemfiles/rails_71/Gemfile.lock index 45235abcedb..e2cfe873003 100644 --- a/gemfiles/rails_71/Gemfile.lock +++ b/gemfiles/rails_71/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: ../.. specs: - activeadmin (4.0.0.beta10) + activeadmin (4.0.0.beta11) arbre (~> 2.0) csv formtastic (>= 3.1) @@ -374,10 +374,10 @@ GEM actionpack (>= 6.1) activesupport (>= 6.1) sprockets (>= 3.0.0) - sqlite3 (1.7.3) + sqlite3 (2.0.4) mini_portile2 (~> 2.8.0) - sqlite3 (1.7.3-arm64-darwin) - sqlite3 (1.7.3-x86_64-linux) + sqlite3 (2.0.4-arm64-darwin) + sqlite3 (2.0.4-x86_64-linux-gnu) stringio (3.1.1) strscan (3.1.0) sys-uname (1.3.0) @@ -430,7 +430,7 @@ DEPENDENCIES simplecov simplecov-cobertura sprockets-rails - sqlite3 (~> 1.7) + sqlite3 webrick BUNDLED WITH diff --git a/lib/active_admin/version.rb b/lib/active_admin/version.rb index 8e9375b33d3..a67922545d2 100644 --- a/lib/active_admin/version.rb +++ b/lib/active_admin/version.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true module ActiveAdmin - VERSION = "4.0.0.beta10" + VERSION = "4.0.0.beta11" end diff --git a/lib/active_admin/views/components/attributes_table.rb b/lib/active_admin/views/components/attributes_table.rb index 080c63d3c0e..ea13471f050 100644 --- a/lib/active_admin/views/components/attributes_table.rb +++ b/lib/active_admin/views/components/attributes_table.rb @@ -23,6 +23,7 @@ def rows(*attrs) def row(*args, &block) title = args[0] + data = args[1] || args[0] options = args.extract_options! options["data-row"] = title.to_s.parameterize(separator: "_") if title.present? @@ -32,7 +33,7 @@ def row(*args, &block) end @collection.each do |record| td do - content_for(record, block || title) + content_for(record, block || data) end end end diff --git a/lib/active_admin/views/components/paginated_collection.rb b/lib/active_admin/views/components/paginated_collection.rb index ee1395c776f..3169d537628 100644 --- a/lib/active_admin/views/components/paginated_collection.rb +++ b/lib/active_admin/views/components/paginated_collection.rb @@ -102,7 +102,10 @@ def build_pagination # you pass in the :total_pages option. We issue a query to determine # if there is another page or not, but the limit/offset make this # query fast. - offset = @collection.offset(@collection.current_page * @collection.limit_value).limit(1).count + offset_scope = @collection.offset(@collection.current_page * @collection.limit_value) + # Support array collections. Kaminari::PaginatableArray does not respond to except + offset_scope = offset_scope.except(:select, :order) if offset_scope.respond_to?(:except) + offset = offset_scope.limit(1).count options[:total_pages] = @collection.current_page + offset options[:right] = 0 end diff --git a/package.json b/package.json index 1e5f16c6f2a..67930cdf52c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@activeadmin/activeadmin", - "version": "4.0.0-beta10", + "version": "4.0.0-beta11", "description": "The administration framework for Ruby on Rails.", "main": "dist/active_admin.js", "type": "module", @@ -39,7 +39,7 @@ "prepublishOnly": "npm run build" }, "dependencies": { - "@rails/ujs": "7.1.2", + "@rails/ujs": "7.1.400", "flowbite": "2.3.0" } } diff --git a/plugin.js b/plugin.js index d0646d57db5..cab6daf64cb 100644 --- a/plugin.js +++ b/plugin.js @@ -312,7 +312,7 @@ module.exports = plugin( '[type=radio]': { '@apply w-4 h-4 border-gray-300 focus:ring-2 focus:ring-blue-300 dark:focus:ring-blue-600 dark:focus:bg-blue-600 dark:bg-gray-700 dark:border-gray-600': {} }, - [['[type=date]', '[type=email]', '[type=number]', '[type=password]', '[type=tel]', '[type=text]', '[type=time]', '[type=url]', 'select', 'textarea']]: { + [['[type=datetime-local]', '[type=month]', '[type=week]', '[type=search]', '[type=date]', '[type=email]', '[type=number]', '[type=password]', '[type=tel]', '[type=text]', '[type=time]', '[type=url]', 'select', 'textarea']]: { '@apply bg-gray-50 border border-gray-300 text-gray-900 rounded-md focus:ring-blue-500 focus:border-blue-500 w-full dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500': {} }, 'a': { diff --git a/spec/helpers/display_helper_spec.rb b/spec/helpers/display_helper_spec.rb index aff9cf6d995..9860435d109 100644 --- a/spec/helpers/display_helper_spec.rb +++ b/spec/helpers/display_helper_spec.rb @@ -223,6 +223,22 @@ class ThisModel expect(value.to_s).to eq "Yes\n" end + context "with non-database boolean attribute" do + let(:model_class) do + Class.new(Post) do + attribute :a_virtual_attribute, :boolean + end + end + + it "calls status_tag even when attribute is nil" do + post = model_class.new a_virtual_attribute: nil + + value = helper.format_attribute post, :a_virtual_attribute + + expect(value.to_s).to eq "Unknown\n" + end + end + it "calls status_tag for boolean non-database values" do post = Post.new post.define_singleton_method(:true_method) do diff --git a/spec/support/matchers/perform_database_query_matcher.rb b/spec/support/matchers/perform_database_query_matcher.rb index 7fc0b79d16c..9f44e364103 100644 --- a/spec/support/matchers/perform_database_query_matcher.rb +++ b/spec/support/matchers/perform_database_query_matcher.rb @@ -2,10 +2,12 @@ RSpec::Matchers.define :perform_database_query do |query| match do |block| + query_regexp = query.is_a?(Regexp) ? query : Regexp.new(Regexp.escape(query)) + @match = nil callback = lambda do |_name, _started, _finished, _unique_id, payload| - @match = Regexp.new(Regexp.escape(query)).match?(payload[:sql]) + @match = query_regexp.match?(payload[:sql]) end ActiveSupport::Notifications.subscribed(callback, "sql.active_record", &block) diff --git a/spec/support/rails_template.rb b/spec/support/rails_template.rb index 012ed3cbcec..93047e52d91 100644 --- a/spec/support/rails_template.rb +++ b/spec/support/rails_template.rb @@ -100,10 +100,11 @@ def self.ransackable_associations(auth_object=nil) # Add some translations append_file "config/locales/en.yml", File.read(File.expand_path("templates/en.yml", __dir__)) -# Add predefined admin resources +# Add predefined admin resources, override any file that was generated by rails new generator directory File.expand_path("templates/admin", __dir__), "app/admin" directory File.expand_path("templates/views", __dir__), "app/views" directory File.expand_path("templates/policies", __dir__), "app/policies" +directory File.expand_path("templates/public", __dir__), "public", force: true route "root to: redirect('admin')" if ENV["RAILS_ENV"] != "test" diff --git a/spec/support/templates/public/favicon.ico b/spec/support/templates/public/favicon.ico new file mode 100644 index 00000000000..db016de0d55 Binary files /dev/null and b/spec/support/templates/public/favicon.ico differ diff --git a/spec/unit/views/components/attributes_table_spec.rb b/spec/unit/views/components/attributes_table_spec.rb index b6db8b06870..3b54cc19082 100644 --- a/spec/unit/views/components/attributes_table_spec.rb +++ b/spec/unit/views/components/attributes_table_spec.rb @@ -47,6 +47,15 @@ end end }, + "when you create each row with a string and symbol" => proc { + render_arbre_component(assigns) do + attributes_table_for post do + row "Id", :id + row "Title", :title + row "Body", :body + end + end + }, "when you create each row with a custom block that returns nil" => proc { render_arbre_component(assigns) do attributes_table_for post do diff --git a/spec/unit/views/components/paginated_collection_spec.rb b/spec/unit/views/components/paginated_collection_spec.rb index 81ac65a8f91..b4e36669d85 100644 --- a/spec/unit/views/components/paginated_collection_spec.rb +++ b/spec/unit/views/components/paginated_collection_spec.rb @@ -229,16 +229,43 @@ def paginated_collection(*args) end end - it "makes no expensive COUNT queries when pagination_total is false" do - undecorated_collection = Post.all.page(1).per(30) + describe "when pagination_total is false" do + it "makes no expensive COUNT queries" do + undecorated_collection = Post.all.page(1).per(30) + + expect { paginated_collection(undecorated_collection, pagination_total: false) } + .not_to perform_database_query("SELECT COUNT(*) FROM \"posts\"") + + decorated_collection = controller_with_decorator("index", PostDecorator).apply_collection_decorator(undecorated_collection.reset) + + expect { paginated_collection(decorated_collection, pagination_total: false) } + .not_to perform_database_query("SELECT COUNT(*) FROM \"posts\"") + end + + it "makes a performant COUNT query to figure out if we are on the last page" do + # "SELECT COUNT(*) FROM (SELECT 1". Let's make sure the subquery has LIMIT and OFFSET. It shouldn't have ORDER BY + count_query = %r{SELECT COUNT\(\*\) FROM \(SELECT 1 .*FROM "posts" (?=.*OFFSET \?)(?=.*LIMIT \?)(?!.*ORDER BY)} - expect { paginated_collection(undecorated_collection, pagination_total: false) } - .not_to perform_database_query("SELECT COUNT(*) FROM \"posts\"") + undecorated_collection = Post.all.page(1).per(30) - decorated_collection = controller_with_decorator("index", PostDecorator).apply_collection_decorator(undecorated_collection) + expect { paginated_collection(undecorated_collection, pagination_total: false) } + .to perform_database_query(count_query) - expect { paginated_collection(decorated_collection, pagination_total: false) } - .not_to perform_database_query("SELECT COUNT(*) FROM \"posts\"") + undecorated_sorted_collection = undecorated_collection.reset.order(id: :desc) + + expect { paginated_collection(undecorated_sorted_collection, pagination_total: false) } + .to perform_database_query(count_query) + + decorated_collection = controller_with_decorator("index", PostDecorator).apply_collection_decorator(undecorated_collection.reset) + + expect { paginated_collection(decorated_collection, pagination_total: false) } + .to perform_database_query(count_query) + + decorated_sorted_collection = controller_with_decorator("index", PostDecorator).apply_collection_decorator(undecorated_sorted_collection.reset) + + expect { paginated_collection(decorated_sorted_collection, pagination_total: false) } + .to perform_database_query(count_query) + end end it "makes no COUNT queries to figure out the last element of each page" do diff --git a/tasks/bug_report_template.rb b/tasks/bug_report_template.rb index 2f398f0abc2..18211de92ff 100644 --- a/tasks/bug_report_template.rb +++ b/tasks/bug_report_template.rb @@ -13,11 +13,11 @@ end # Change Rails version if necessary. - gem "rails", "~> 7.1.0" + gem "rails", "~> 7.2.0" gem "sprockets", "~> 4.0" gem "importmap-rails", "~> 2.0" - gem "sqlite3", "~> 1.x", force_ruby_platform: true, platform: :mri + gem "sqlite3", force_ruby_platform: true, platform: :mri # Fixes an issue on CI with default gems when using inline bundle with default # gems that are already activated @@ -54,6 +54,7 @@ class TestApp < Rails::Application config.hosts << ".example.com" config.session_store :cookie_store, key: "cookie_store_key" config.secret_key_base = "secret_key_base" + config.eager_load = false config.logger = Logger.new($stdout) Rails.logger = config.logger diff --git a/yarn.lock b/yarn.lock index 2f80abe4a1d..ab0610b595d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -191,10 +191,10 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== -"@rails/ujs@7.1.2": - version "7.1.2" - resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-7.1.2.tgz#ea903bcc0224e17156015d995b6f1b83e27d64b2" - integrity sha512-c5x02djEKEVVE4qfN4XgElJS4biM0xxtIVpcJ0ZHLK116U19rowTtmD0AJ/RCb3Xaewa4GPIWLlwgeC0dCQqzw== +"@rails/ujs@7.1.400": + version "7.1.400" + resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-7.1.400.tgz#b2a76bdccb5197b9e866954536106386c87cfab5" + integrity sha512-YwvXm3BR5tn+VCAKYGycLejMRVZE3Ionj5gFjEeGXCZnI0Rpi+7dKpmyu90kdUY7dRUFpHTdu9zZceEzFLl38w== "@rollup/plugin-alias@^5.1.0": version "5.1.0" @@ -395,7 +395,7 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.2, braces@~3.0.2: +braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== @@ -979,11 +979,11 @@ merge2@^1.3.0: integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== micromatch@^4.0.4, micromatch@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" mini-svg-data-uri@^1.4.3: