Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

ActiveAdmin::Resource looks up classes at runtime using their name

To deal with the reloading issues in #870, we now store reference to the
resource class as a string and constantize it each time we need it.

Also added a new cucumber profile called "class-reloading" which does
not cache classes. Since Active Admin should always work as expected in
development with Rails reloading, we should continue to grow scenarios
that include the '@requires-reloading' tag.
  • Loading branch information...
commit 572dbf1ffafcdc30d41aa2146b4a49ffb8b1c32d 1 parent 6a4efe3
Greg Bell authored December 24, 2011
5  cucumber.yml
... ...
@@ -1,2 +1,3 @@
1  
-default: --format 'progress' --require features/support/env.rb --require features/step_definitions features
2  
-wip: --format 'progress' --require features/support/env.rb --require features/step_definitions features --tags @wip:3 --wip features
  1
+default: --format 'progress' --require features/support/env.rb --require features/step_definitions features --tags ~@requires-reloading
  2
+wip: --format 'progress' --require features/support/env.rb --require features/step_definitions features --tags @wip:3 --wip features
  3
+class-reloading: RAILS_ENV=cucumber_with_reloading --format 'progress' --require features/support/env.rb --require features/step_definitions features --tags @requires-reloading
19  features/development_reloading.feature
... ...
@@ -0,0 +1,19 @@
  1
+Feature: Development Reloading
  2
+
  3
+  In order to quickly develop applications
  4
+  As a developer
  5
+  I want the application to reload itself in development
  6
+
  7
+  @requires-reloading
  8
+  Scenario: Reloading an updated model that a resource points to
  9
+    Given a configuration of:
  10
+    """
  11
+      ActiveAdmin.register Post
  12
+    """
  13
+    And I am logged in
  14
+    And I create a new post with the title ""
  15
+    Then I should see a successful create flash
  16
+    Given I add "validates_presence_of :title" to the "post" model
  17
+    And I create a new post with the title ""
  18
+    Then I should not see a successful create flash
  19
+    And I should see a validation error "can't be blank"
3  features/step_definitions/additional_web_steps.rb
@@ -80,3 +80,6 @@
80 80
   page.should have_css("#active_admin_content", :text => content)
81 81
 end
82 82
 
  83
+Then /^I should see a validation error "([^"]*)"$/ do |error_message|
  84
+  page.should have_css(".inline-errors", :text => error_message)
  85
+end
40  features/step_definitions/configuration_steps.rb
@@ -9,8 +9,35 @@ def load_active_admin_configuration(configuration_content)
9 9
 
10 10
 end
11 11
 
  12
+module ActiveAdminContentsRollback
  13
+
  14
+  def self.recorded_files
  15
+    @files ||= {}
  16
+  end
  17
+
  18
+  # Records the contents of a file the first time we are
  19
+  # about to change it
  20
+  def self.record(filename)
  21
+    recorded_files[filename] ||= File.read(filename)
  22
+  end
  23
+
  24
+  # Rolls the recorded files back to their original states
  25
+  def self.rollback!
  26
+    recorded_files.each do |filename, contents|
  27
+      File.open(filename, "w+") do |f|
  28
+        f << contents
  29
+      end
  30
+    end
  31
+  end
  32
+
  33
+end
  34
+
12 35
 World(ActiveAdminReloading)
13 36
 
  37
+After do
  38
+  ActiveAdminContentsRollback.rollback!
  39
+end
  40
+
14 41
 Given /^a configuration of:$/ do |configuration_content|
15 42
   load_active_admin_configuration(configuration_content)
16 43
   ActiveAdmin.application.namespaces.values.each{|n| n.load_menu! }
@@ -37,3 +64,16 @@ def load_active_admin_configuration(configuration_content)
37 64
   FileUtils.mkdir_p File.dirname(filepath)
38 65
   File.open(filepath, 'w+'){|f| f << contents }
39 66
 end
  67
+
  68
+Given /^I add "([^"]*)" to the "([^"]*)" model$/ do |code, model_name|
  69
+  filename = File.join(Rails.root, "app", "models", "#{model_name}.rb")
  70
+  ActiveAdminContentsRollback.record(filename)
  71
+
  72
+  # Update the file
  73
+  contents = File.read(filename)
  74
+  File.open(filename, "w+") do |f|
  75
+    f << contents.gsub(/^(class .+)$/, "\\1\n  #{code}\n")
  76
+  end
  77
+
  78
+  ActiveSupport::Dependencies.clear
  79
+end
10  features/step_definitions/flash_steps.rb
... ...
@@ -1,3 +1,11 @@
1 1
 Then /^I should see a flash with "([^"]*)"$/ do |text|
2  
-  Then %{I should see "#{text}"}
  2
+  page.should have_content(text)
  3
+end
  4
+
  5
+Then /^I should see a successful create flash$/ do
  6
+  page.should have_css('div.flash_notice', :text => /was successfully created/)
  7
+end
  8
+
  9
+Then /^I should not see a successful create flash$/ do
  10
+  page.should_not have_css('div.flash_notice', :text => /was successfully created/)
3 11
 end
13  lib/active_admin/resource.rb
@@ -25,9 +25,8 @@ class Resource
25 25
     # The namespace this config belongs to
26 26
     attr_reader :namespace
27 27
 
28  
-    # The class this resource wraps. If you register the Post model, Resource#resource
29  
-    # will point to the Post class
30  
-    attr_reader :resource_class
  28
+    # The name of the resource class
  29
+    attr_reader :resource_class_name
31 30
 
32 31
     # An array of member actions defined for this resource
33 32
     attr_reader :member_actions
@@ -50,7 +49,7 @@ class Resource
50 49
     module Base
51 50
       def initialize(namespace, resource_class, options = {})
52 51
         @namespace = namespace
53  
-        @resource_class = resource_class
  52
+        @resource_class_name = "::#{resource_class.name}"
54 53
         @options = default_options.merge(options)
55 54
         @sort_order = @options[:sort_order]
56 55
         @member_actions, @collection_actions = [], []
@@ -66,6 +65,12 @@ def initialize(namespace, resource_class, options = {})
66 65
     include Sidebars
67 66
     include Menu
68 67
 
  68
+    # The class this resource wraps. If you register the Post model, Resource#resource_class
  69
+    # will point to the Post class
  70
+    def resource_class
  71
+      ActiveSupport::Dependencies.constantize(resource_class_name)
  72
+    end
  73
+
69 74
     def resource_table_name
70 75
       resource_class.quoted_table_name
71 76
     end
11  lib/active_admin/resource_controller.rb
@@ -5,6 +5,7 @@
5 5
 require 'active_admin/resource_controller/collection'
6 6
 require 'active_admin/resource_controller/filters'
7 7
 require 'active_admin/resource_controller/scoping'
  8
+require 'active_admin/resource_controller/resource_class_methods'
8 9
 
9 10
 module ActiveAdmin
10 11
   # All Resources Controller inherits from this controller.
@@ -23,15 +24,25 @@ class ResourceController < BaseController
23 24
     include Collection
24 25
     include Filters
25 26
     include Scoping
  27
+    extend  ResourceClassMethods
26 28
 
27 29
     class << self
28 30
       def active_admin_config=(config)
29 31
         @active_admin_config = config
  32
+
30 33
         defaults  :resource_class => config.resource_class,
31 34
                   :route_prefix => config.route_prefix,
32 35
                   :instance_name => config.underscored_resource_name
33 36
       end
34 37
 
  38
+      # Inherited Resources uses the inherited(base) hook method to 
  39
+      # add in the Base.resource_class class method. To override it, we
  40
+      # need to install our resource_class method each time we're inherited from.
  41
+      def inherited(base)
  42
+        super(base)
  43
+        base.override_resource_class_methods!
  44
+      end
  45
+
35 46
       public :belongs_to
36 47
     end
37 48
 
24  lib/active_admin/resource_controller/resource_class_methods.rb
... ...
@@ -0,0 +1,24 @@
  1
+module ActiveAdmin
  2
+  class ResourceController < BaseController
  3
+    module ResourceClassMethods
  4
+
  5
+      # Override the default resource_class class and instance
  6
+      # methods to only return the class defined in the instance
  7
+      # of ActiveAdmin::Resource
  8
+      def override_resource_class_methods!
  9
+        self.class_eval do
  10
+          def self.resource_class=(klass); end
  11
+
  12
+          def self.resource_class
  13
+            @active_admin_config ? @active_admin_config.resource_class : nil
  14
+          end
  15
+
  16
+          def resource_class
  17
+            self.class.resource_class
  18
+          end
  19
+        end
  20
+      end
  21
+
  22
+    end
  23
+  end
  24
+end
4  spec/spec_helper.rb
@@ -90,6 +90,10 @@ def mock_action_view(assigns = {})
90 90
   end  
91 91
   alias_method :action_view, :mock_action_view
92 92
 
  93
+  # A mock resource to register
  94
+  class MockResource
  95
+  end
  96
+
93 97
 end
94 98
 
95 99
 ENV['RAILS_ENV'] = 'test'
3  spec/support/rails_template.rb
@@ -2,8 +2,11 @@
2 2
 
3 3
 # Create a cucumber database and environment
4 4
 copy_file File.expand_path('../templates/cucumber.rb', __FILE__), "config/environments/cucumber.rb"
  5
+copy_file File.expand_path('../templates/cucumber_with_reloading.rb', __FILE__), "config/environments/cucumber_with_reloading.rb"
  6
+
5 7
 gsub_file 'config/database.yml', /^test:.*\n/, "test: &test\n"
6 8
 gsub_file 'config/database.yml', /\z/, "\ncucumber:\n  <<: *test\n  database: db/cucumber.sqlite3"
  9
+gsub_file 'config/database.yml', /\z/, "\ncucumber_with_reloading:\n  <<: *test\n  database: db/cucumber.sqlite3"
7 10
 
8 11
 # Generate some test models
9 12
 generate :model, "post title:string body:text published_at:datetime author_id:integer category_id:integer"
5  spec/support/templates/cucumber_with_reloading.rb
... ...
@@ -0,0 +1,5 @@
  1
+require File.expand_path('config/environments/cucumber', Rails.root)
  2
+
  3
+Rails.application.class.configure do
  4
+  config.cache_classes = false
  5
+end
1  spec/unit/namespace/register_resource_spec.rb
@@ -52,6 +52,7 @@ module ::Mock; class Resource; def self.has_many(arg1, arg2); end; end; end
52 52
       namespace.load_menu!
53 53
       namespace.menu["Mock Resources"].should be_an_instance_of(ActiveAdmin::MenuItem)
54 54
     end
  55
+
55 56
     it "should use the resource as the model in the controller" do
56 57
       Admin::MockResourcesController.resource_class.should == Mock::Resource
57 58
     end
8  spec/unit/resource_spec.rb
@@ -200,17 +200,15 @@ class ::News; def self.has_many(*); end end
200 200
 
201 201
       context "when resource class responds to primary_key" do
202 202
         it "should sort by primary key desc by default" do
203  
-          mock_resource = mock
204  
-          mock_resource.should_receive(:primary_key).and_return("pk")
205  
-          config = Resource.new(namespace, mock_resource)
  203
+          MockResource.should_receive(:primary_key).and_return("pk")
  204
+          config = Resource.new(namespace, MockResource)
206 205
           config.sort_order.should == "pk_desc"
207 206
         end
208 207
       end
209 208
 
210 209
       context "when resource class does not respond to primary_key" do
211 210
         it "should default to id" do
212  
-          mock_resource = mock
213  
-          config = Resource.new(namespace, mock_resource)
  211
+          config = Resource.new(namespace, MockResource)
214 212
           config.sort_order.should == "id_desc"
215 213
         end
216 214
       end
7  tasks/test.rake
@@ -7,7 +7,7 @@ end
7 7
 
8 8
 # Run specs and cukes
9 9
 desc "Run the full suite"
10  
-task :test => ['spec:unit', 'spec:integration', 'cucumber']
  10
+task :test => ['spec:unit', 'spec:integration', 'cucumber', 'cucumber:class_reloading']
11 11
 
12 12
 namespace :test do
13 13
 
@@ -28,6 +28,7 @@ namespace :test do
28 28
       cmd "export RAILS=#{version} && bundle exec rspec spec/unit"
29 29
       cmd "export RAILS=#{version} && bundle exec rspec spec/integration"
30 30
       cmd "export RAILS=#{version} && bundle exec cucumber features"
  31
+      cmd "export RAILS=#{version} && bundle exec cucumber -p class-reloading features"
31 32
     end
32 33
     cmd "./script/use_rails #{current_version}" if current_version
33 34
   end
@@ -65,4 +66,8 @@ namespace :cucumber do
65 66
     t.profile = 'wip'
66 67
   end
67 68
 
  69
+  Cucumber::Rake::Task.new(:class_reloading, "Run the cucumber scenarios that test reloading") do |t|
  70
+    t.profile = 'class-reloading'
  71
+  end
  72
+
68 73
 end

0 notes on commit 572dbf1

Please sign in to comment.
Something went wrong with that request. Please try again.