resource: allow resource_class to be passed as a String#8848
resource: allow resource_class to be passed as a String#8848tagliala merged 2 commits intoactiveadmin:masterfrom
Conversation
|
Hello, thanks for this PR, it looks like a good change Can you please:
|
f42b2fe to
7d1da8f
Compare
7776685 to
7234b87
Compare
|
@tagliala thx for the review, addressed all points.
This is trying to improve dev-local performance of using activeadmin with autoloading enabled; while resources get eager-loaded as part of the route injection, it doesn't really need to load the resources until the page is loaded. |
Thanks. I did try to measure the performance gain, but I'm afraid I did not succeed (Rails 8.1 / Ruby 3.4 / Apple Silicon M1 Pro). I've tried to enable PS: is this also related to #8782 ? Also involving @javierjulio and @mgrunberg . This is like the rails change in |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #8848 +/- ##
=======================================
Coverage 99.08% 99.08%
=======================================
Files 139 139
Lines 4046 4046
=======================================
Hits 4009 4009
Misses 37 37 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
It's highly dependent on the size of the application and what you're doing at boot time. I've enabled this in our app as a monkeypatch, and the overall gains from reenabling autoloading (not only including activeadmin changes) amounted to ~10s saved when opening a console / running a spec touching 1-3 models. And I can confirm it's related to what you linked. |
|
@HoneyryderChuck thanks I really would have the ability to test this by myself. I've tried against a different application with more models without success, I do not see benefits. Any chance to provide information about the Rails version used, the ruby version, some kind of Or maybe to edit https://github.com/activeadmin/demo.activeadmin.info to demonstrate the improvement
Is this change going to make #8782 unnecessary? Or, if both approaches are used, is this going to boost the performance even more? |
tagliala
left a comment
There was a problem hiding this comment.
Hi @HoneyryderChuck,
I appreciate the change and am inclined to merge it as a best practice. That said, I'd like to quantify its benefits and uncover any potential issues related to early-loading classes.
Could you share a reproducible repository, ideally based on activeadmin/demo.activeadmin.info, along with a clear procedure for measuring load times?
This would help me validate the impact for the team and proceed with confidence
|
|
||
| it "allows registering with string" do | ||
| namespace = ActiveAdmin::Namespace.new(ActiveAdmin::Application.new, :admin) | ||
| resource = namespace.register("Post") | ||
| resource.clear_action_items! | ||
| resource.add_action_item :empty do | ||
| # Empty ... | ||
| end | ||
| expect(resource.action_items.size).to eq 1 | ||
| end | ||
| end |
There was a problem hiding this comment.
If you can move this spec to the namespace and check for the string registration, something like
diff --git a/spec/unit/namespace/register_resource_spec.rb b/spec/unit/namespace/register_resource_spec.rb
index c97cc181..0176bd1a 100644
--- a/spec/unit/namespace/register_resource_spec.rb
+++ b/spec/unit/namespace/register_resource_spec.rb
@@ -31,6 +31,16 @@ RSpec.describe ActiveAdmin::Namespace, "registering a resource" do
end
end
+ context "when resource class is a string" do
+ before do
+ namespace.register 'Category'
+ end
+
+ it "should store the namespaced registered configuration" do
+ expect(namespace.resources.keys).to include("Category")
+ end
+ end
+
context "with a block configuration" do
it "should be evaluated in the dsl" do
expect do |block|
diff --git a/spec/unit/resource/action_items_spec.rb b/spec/unit/resource/action_items_spec.rb
index 2fea4fbc..0dd26177 100644
--- a/spec/unit/resource/action_items_spec.rb
+++ b/spec/unit/resource/action_items_spec.rb
@@ -71,14 +71,4 @@ RSpec.describe ActiveAdmin::Resource::ActionItems do
expect(resource.action_items.size).to eq 2
end
end
-
- it "allows registering with string" do
- namespace = ActiveAdmin::Namespace.new(ActiveAdmin::Application.new, :admin)
- resource = namespace.register("Post")
- resource.clear_action_items!
- resource.add_action_item :empty do
- # Empty ...
- end
- expect(resource.action_items.size).to eq 1
- end
end|
@tagliala sorry haven't had the time to go back to this, as providing this minimal change is less overhead than providing a full blown app which measures the impact. I honestly believe that this is only noticeable in really big applications with convoluted boot time model logic, so the mileage always varies, and a reproduction will be of marginal value. |
the current pattern of using the class directly loads all models bound to activeadmin before using them, needlessly. besides that, the only purpose of it is to store the class name this patch allows one to pass the class name directly instead, kind of like how activerecord avoids eager-loading association classes.
|
@tagliala from what you shared in Slack, the change makes sense and I think should be safe enough. Let's move the test case since as you shown we already have a spec for namespace registration. Thank you. |
|
@javierjulio I've AI-generated a script to create thousands of models, and I'm still not able to replicate or see differences in loading time. I've tested with 1k models I've tested both I'm using The "solution" seems trivial, but it would be good to be able to understand the correct issue here. Maybe it is a different version of Ruby or Rails? Classic and not zeitwerk mode? Script: #!/usr/bin/env ruby
require "fileutils"
require "active_support/inflector"
# CONFIGURABLE:
MODEL_PREFIX = "ExampleModel" # hardcoded for demo, could make configurable
MODEL_DIR = "app/models"
ADMIN_DIR = "app/admin"
MIGRATION_DIR = "db/migrate"
def usage
puts "Usage: #{$0} <number_of_models>"
exit 1
end
unless ARGV.size == 1 && ARGV[0] =~ /^\d+$/
usage
end
num_models = ARGV[0].to_i
raise "Number must be > 0" if num_models < 1
models = (1..num_models).map { |i| "#{MODEL_PREFIX}#{i}" }
# 1. Remove previous models and active admin files for these models
models.each do |model|
model_file = File.join(MODEL_DIR, "#{model.underscore}.rb")
File.delete(model_file) if File.exist?(model_file)
admin_file = File.join(ADMIN_DIR, "#{model.underscore}.rb")
File.delete(admin_file) if File.exist?(admin_file)
end
# 2. Generate and run a migration to drop all the tables for these models
drop_migration_name = "drop_example_model_tables_#{Time.now.strftime('%Y%m%d%H%M%S')}"
drop_migration_file = File.join(MIGRATION_DIR, "#{Time.now.strftime('%Y%m%d%H%M%S')}_#{drop_migration_name}.rb")
drop_migration_content = <<~RUBY
class #{drop_migration_name.camelize} < ActiveRecord::Migration[6.1]
def change
#{models.map { |model|
"drop_table :#{model.underscore.pluralize} if table_exists?(:#{model.underscore.pluralize})"
}.join("\n ")}
end
end
RUBY
File.write(drop_migration_file, drop_migration_content)
puts "Generated migration to drop previous tables: #{drop_migration_file}"
system("bin/rails db:migrate") # runs the drop migration
# Remove the drop migration file after use (optional for idempotency)
File.delete(drop_migration_file) if File.exist?(drop_migration_file)
# 3. Generate model files
models.each do |model|
model_file = File.join(MODEL_DIR, "#{model.underscore}.rb")
File.write(model_file, <<~RUBY)
class #{model} < ApplicationRecord
end
RUBY
puts "Created model #{model_file}"
end
# 4. Generate migration to create tables
create_migration_name = "create_example_model_tables_#{Time.now.strftime('%Y%m%d%H%M%S')}"
create_migration_file = File.join(MIGRATION_DIR, "#{Time.now.strftime('%Y%m%d%H%M%S')}_#{create_migration_name}.rb")
create_migration_content = <<~RUBY
class #{create_migration_name.camelize} < ActiveRecord::Migration[6.1]
def change
#{models.map { |model|
<<~TABLE
create_table :#{model.underscore.pluralize} do |t|
t.string :name
t.timestamps
end
TABLE
}.join("\n ")}
end
end
RUBY
File.write(create_migration_file, create_migration_content)
puts "Generated migration to create tables: #{create_migration_file}"
system("bin/rails db:migrate") # runs the create migration
# 5. Create ActiveAdmin config files
models.each do |model|
admin_file = File.join(ADMIN_DIR, "#{model.underscore}.rb")
File.write(admin_file, <<~RUBY)
# frozen_string_literal: true
ActiveAdmin.register #{model} do
end
RUBY
puts "Created ActiveAdmin config: #{admin_file}"
end
puts "Done. Idempotent models and tables created for: #{models.join(', ')}"
1k models, Eager load: false, non-string1k models, Eager load: false, strings |
7234b87 to
c7651ba
Compare
the current pattern of using the class directly loads all models bound to activeadmin before using them, needlessly. besides that, the only purpose of it is to store the class name
this patch allows one to pass the class name directly instead, kind of like how activerecord avoids eager-loading association classes.