diff --git a/lib/rails_admin/config.rb b/lib/rails_admin/config.rb index 03e72acb78..592aa12ff0 100644 --- a/lib/rails_admin/config.rb +++ b/lib/rails_admin/config.rb @@ -226,11 +226,9 @@ def model(entity, &block) end end - if block - @registry[key] = RailsAdmin::Config::LazyModel.new(entity, &block) - else - @registry[key] ||= RailsAdmin::Config::LazyModel.new(entity) - end + @registry[key] ||= RailsAdmin::Config::LazyModel.new(entity) + @registry[key].add_deferred_block(&block) if block + @registry[key] end def default_hidden_fields=(fields) diff --git a/lib/rails_admin/config/lazy_model.rb b/lib/rails_admin/config/lazy_model.rb index efc60fa427..dc0d9bfd1f 100644 --- a/lib/rails_admin/config/lazy_model.rb +++ b/lib/rails_admin/config/lazy_model.rb @@ -5,13 +5,53 @@ module Config class LazyModel < BasicObject def initialize(entity, &block) @entity = entity - @deferred_block = block + @deferred_blocks = [*block] + @existing_blocks = [] + end + + def add_deferred_block(&block) + @deferred_blocks << block end def target - unless @model - @model = ::RailsAdmin::Config::Model.new(@entity) - @model.instance_eval(&@deferred_block) if @deferred_block + @model ||= ::RailsAdmin::Config::Model.new(@entity) + # When evaluating multiple configuration blocks, the order of + # execution is important. As one would expect (in my opinion), + # options defined within a resource should take precedence over + # more general options defined in an initializer. This way, + # general settings for a number of resources could be specified + # in the initializer, while models could override these settings + # later, if required. + # + # CAVEAT: It cannot be guaranteed that blocks defined in an initializer + # will be loaded (and adde to @deferred_blocks) first. For instance, if + # the initializer references a model class before defining + # a RailsAdmin configuration block, the configuration from the + # resource will get added to @deferred_blocks first: + # + # # app/models/some_model.rb + # class SomeModel + # rails_admin do + # : + # end + # end + # + # # config/initializers/rails_admin.rb + # model = 'SomeModel'.constantize # blocks from SomeModel get loaded + # model.config model do # blocks from initializer gets loaded + # : + # end + # + # Thus, sort all blocks to excute for a resource by Proc.source_path, + # to guarantee that blocks from 'config/initializers' evaluate before + # blocks defined within a model class. + unless @deferred_blocks.empty? + @existing_blocks += @deferred_blocks + @existing_blocks. + partition { |block| block.source_location.first =~ %r{config\/initializers} }. + flatten. + each { |block| @model.instance_eval(&block) } + @deferred_blocks = [] end @model end diff --git a/spec/rails_admin/config_spec.rb b/spec/rails_admin/config_spec.rb index eaa77a66aa..352b012e17 100644 --- a/spec/rails_admin/config_spec.rb +++ b/spec/rails_admin/config_spec.rb @@ -270,6 +270,47 @@ class RecursivelyEmbedsMany expect(RailsAdmin.config.parent_controller).to eq 'TestController' end end + + describe '.model' do + let(:fields) { described_class.model(Team).fields } + before do + described_class.model Team do + field :players do + visible false + end + end + end + context 'when model expanded' do + before do + described_class.model(Team) do + field :fans + end + end + it 'execute all passed blocks' do + expect(fields.map(&:name)).to match_array %i(players fans) + end + end + context 'when expand redefine behavior' do + before do + described_class.model Team do + field :players + end + end + it 'execute all passed blocks' do + expect(fields.find { |f| f.name == :players }.visible).to be true + end + end + context 'when model expanded in config' do + let(:block) { proc { field :players } } + before do + allow(block).to receive(:source_location).and_return(['config/initializers/rails_admin.rb']) + described_class.model(Team, &block) + end + it 'executes first' do + expect(fields.find { |f| f.name == :players }.visible).to be false + end + end + end end module ExampleModule