diff --git a/.gitignore b/.gitignore index eee7ffa0c8a..165cce23774 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ test-rails* public .rvmrc .rspec +.idea/**/* \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 115687fabd2..148e569137c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,36 @@ Nothing yet +## 0.3.4 + +2 commits by 2 authors + +### Bug Fixes + +* Fix reloading issues across operating systems. +* Fix issue where SASS was recompiling on every request. This can seriously + decrease the load time of applications when running in development mode. + Thanks @dhiemstra for tracking this one down! + +### Contributors + +* Danny Hiemstra +* Greg Bell + +## 0.3.3 + +1 commit by 1 author + +### Enhancements + +* Only reload Active Admin when files in the load paths have changed. This is a + major speed increase in development mode. Also helps with memory consumption + because we aren't reloading Active admin all the time. + +### Contributors + +* Greg Bell + ## 0.3.2 45 commits by 15 contributors diff --git a/features/support/env.rb b/features/support/env.rb index 55074cd03e2..f82a8736b73 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -9,18 +9,24 @@ require File.expand_path('../../../spec/support/detect_rails_version', __FILE__) ENV["RAILS"] = detect_rails_version +ENV["RAILS_ENV"] ||= "cucumber" +ENV['RAILS_ROOT'] = File.expand_path("../../../spec/rails/rails-#{ENV["RAILS"]}", __FILE__) + + require 'rubygems' require "bundler" Bundler.setup -ENV["RAILS_ENV"] ||= "cucumber" -ENV['RAILS_ROOT'] = File.expand_path("../../../spec/rails/rails-#{ENV["RAILS"]}", __FILE__) - # Create the test app if it doesn't exists unless File.exists?(ENV['RAILS_ROOT']) system 'rake setup' end +# Ensure the Active Admin load path is happy +require 'rails' +require 'active_admin' +ActiveAdmin.application.load_paths = [ENV['RAILS_ROOT'] + "/app/admin"] + require ENV['RAILS_ROOT'] + '/config/environment' # Setup autoloading of ActiveAdmin and the load path diff --git a/lib/active_admin/application.rb b/lib/active_admin/application.rb index 5c99af99cc2..7cee1925aa8 100644 --- a/lib/active_admin/application.rb +++ b/lib/active_admin/application.rb @@ -208,7 +208,7 @@ def remove_active_admin_load_paths_from_rails_autoload_and_eager_load end def attach_reloader - ActiveAdmin::Reloader.new(Rails.version).attach! + ActiveAdmin::Reloader.new(Rails.application, self, Rails.version).attach! end diff --git a/lib/active_admin/reloader.rb b/lib/active_admin/reloader.rb index f24e3725751..97c1a3787ad 100644 --- a/lib/active_admin/reloader.rb +++ b/lib/active_admin/reloader.rb @@ -1,23 +1,57 @@ module ActiveAdmin + + class FileUpdateChecker < ::ActiveSupport::FileUpdateChecker + + # Over-ride the default #updated_at to support the deletion of files + def updated_at + paths.map { |path| File.mtime(path) rescue Time.now }.max + end + + end + # Deals with reloading Active Admin on each request in # development and once in production. class Reloader - # @param [String] rails_version - # The version of Rails we're using. We use this to switch between - # the correcr Rails reloader class. - def initialize(rails_version) + attr_reader :active_admin_app, + :rails_app, + :file_update_checker + + # @param [ActiveAdmin::Application] app + # @param [String] rails_version The version of Rails we're using. + # We use this to switch between the correcrt Rails reloader class. + def initialize(rails_app, active_admin_app, rails_version) + @rails_app = rails_app + @active_admin_app = active_admin_app @rails_version = rails_version.to_s + @file_update_checker = FileUpdateChecker.new(watched_paths) do + reload! + end + end + + def reload! + active_admin_app.unload! + rails_app.reload_routes! + file_update_checker.paths.clear + watched_paths.each{|path| file_update_checker.paths << path } end # Attach to Rails and perform the reload on each request. def attach! + # Bring the checker into local scope for the ruby block + checker = file_update_checker + reloader_class.to_prepare do - ActiveAdmin.application.unload! - Rails.application.reload_routes! + checker.execute_if_updated end end + def watched_paths + paths = active_admin_app.load_paths + active_admin_app.load_paths.each{|path| paths += Dir[File.join(path, "**", "*.rb")]} + paths + end + def reloader_class if @rails_version[0..2] == '3.1' ActionDispatch::Reloader diff --git a/lib/active_admin/resource/action_items.rb b/lib/active_admin/resource/action_items.rb index 58c8531a734..4e8c292d8b8 100644 --- a/lib/active_admin/resource/action_items.rb +++ b/lib/active_admin/resource/action_items.rb @@ -48,23 +48,23 @@ def clear_action_items! def add_default_action_items # New Link on all actions except :new and :show add_action_item :except => [:new, :show] do - if controller.action_methods.include?('new') - link_to(I18n.t('active_admin.new_model', :model => active_admin_config.resource_name), new_resource_path) + if controller.action_methods.include?('new') && can?(:create, active_admin_config.resource_name.constantize) + link_to(I18n.t('active_admin.new_model', :model => active_admin_config.resource_name), new_resource_path, :class => "active_admin new_action") end end # Edit link on show add_action_item :only => :show do - if controller.action_methods.include?('edit') - link_to(I18n.t('active_admin.edit_model', :model => active_admin_config.resource_name), edit_resource_path(resource)) + if controller.action_methods.include?('edit') && can?(:update, active_admin_config.resource_name.constantize) + link_to(I18n.t('active_admin.edit_model', :model => active_admin_config.resource_name), edit_resource_path(resource), :class => "active_admin edit_action") end end # Destroy link on show add_action_item :only => :show do - if controller.action_methods.include?("destroy") + if controller.action_methods.include?("destroy") && can?(:destroy, active_admin_config.resource_name.constantize) link_to(I18n.t('active_admin.delete_model', :model => active_admin_config.resource_name), - resource_path(resource), + resource_path(resource), :class => "active_admin delete_action", :method => :delete, :confirm => I18n.t('active_admin.delete_confirmation')) end end diff --git a/lib/active_admin/sass/css_loader.rb b/lib/active_admin/sass/css_loader.rb index 2f427db9020..ac78be3d9cc 100644 --- a/lib/active_admin/sass/css_loader.rb +++ b/lib/active_admin/sass/css_loader.rb @@ -9,7 +9,7 @@ class Sass::Importers::Filesystem # We want to ensure that all *.css.scss files are loaded as scss files def extensions_with_css - extensions_without_css.merge('css.scss' => :scss) + extensions_without_css.merge('{css.,}scss' => :scss) end alias_method_chain :extensions, :css diff --git a/lib/active_admin/version.rb b/lib/active_admin/version.rb index 574b4fec531..4d07919bf8d 100644 --- a/lib/active_admin/version.rb +++ b/lib/active_admin/version.rb @@ -1,3 +1,3 @@ module ActiveAdmin - VERSION = '0.3.2' + VERSION = '0.3.4' end diff --git a/lib/active_admin/views/index_as_table.rb b/lib/active_admin/views/index_as_table.rb index 9762f3355f9..8c1fc3c8165 100644 --- a/lib/active_admin/views/index_as_table.rb +++ b/lib/active_admin/views/index_as_table.rb @@ -121,14 +121,33 @@ def id_column end # Adds links to View, Edit and Delete + # + # To include or exclude specific actions, use the :only and :except options. + # Insert additional markup before or after the default actions by using the :before and :after optinos. If the + # :before or :after options is a Proc, it will be called, with the current resource applied as first argument. + # Sample Usage: default_actions :except => [:delete, :edit] def default_actions(options = {}) options = { - :name => "" + :name => "", + :except => [], + :only => nil, + :before => "", + :after => "" }.merge(options) + + # :except takes precedence over :only. + display = options[:only] || [:view, :edit, :delete] + display.delete_if do |item| options[:except].include?(item) end + + # puts controller.action_methods + column options[:name] do |resource| links = link_to I18n.t('active_admin.view'), resource_path(resource), :class => "member_link view_link" - links += link_to I18n.t('active_admin.edit'), edit_resource_path(resource), :class => "member_link edit_link" - links += link_to I18n.t('active_admin.delete'), resource_path(resource), :method => :delete, :confirm => I18n.t('active_admin.delete_confirmation'), :class => "member_link delete_link" + links += options[:before].is_a?(Proc) ? options[:before].call(resource) : options[:before] + links += link_to I18n.t('active_admin.view'), resource_path(resource), :class => "member_link view_link" if display.include?(:view) + links += link_to I18n.t('active_admin.edit'), edit_resource_path(resource), :class => "member_link edit_link" if display.include?(:edit) && controller.action_methods.include?("edit") + links += link_to I18n.t('active_admin.delete'), resource_path(resource), :method => :delete, :confirm => I18n.t('active_admin.delete_confirmation'), :class => "member_link delete_link" if display.include?(:delete) && controller.action_methods.include?("destroy") + links += options[:after].is_a?(Proc) ? options[:after].call(resource) : options[:after] links end end diff --git a/spec/unit/reloader_spec.rb b/spec/unit/reloader_spec.rb index 65b3efd235a..c8abd35cee1 100644 --- a/spec/unit/reloader_spec.rb +++ b/spec/unit/reloader_spec.rb @@ -4,25 +4,77 @@ begin ActionDispatch::Reloader rescue - module ActionDispatch; module Reloader; end; end + module ActionDispatch; module Reloader; def self.to_prepare; end; end; end end begin ActionDispatch::Callbacks rescue - module ActionDispatch; module Callbacks; end; end + module ActionDispatch; module Callbacks; def self.to_prepare; end; end; end end describe ActiveAdmin::Reloader do - it "should use ActionDispatch::Reloader if rails 3.1" do - reloader = ActiveAdmin::Reloader.new '3.1.0' - reloader.reloader_class.should == ActionDispatch::Reloader + let(:rails_app){ mock(:reload_routes! => true)} + let(:mock_app){ mock(:load_paths => ["app/admin"], :unload! => true)} + let(:reloader){ ActiveAdmin::Reloader.new(rails_app, mock_app, "3.1.0")} + + it "should initialize a new file update checker" do + ActiveSupport::FileUpdateChecker.should_receive(:new).with(mock_app.load_paths).and_return(mock(:execute_if_updated => true)) + ActiveAdmin::Reloader.new(rails_app, mock_app, '3.1.0').attach! + end + + describe "#reloader_class" do + it "should use ActionDispatch::Reloader if rails 3.1" do + reloader = ActiveAdmin::Reloader.new rails_app, mock_app, '3.1.0' + reloader.reloader_class.should == ActionDispatch::Reloader + end + + it "should use ActionDispatch::Callbacks if rails 3.0" do + reloader = ActiveAdmin::Reloader.new rails_app, mock_app, '3.0.0' + reloader.reloader_class.should == ActionDispatch::Callbacks + end + end + + describe "#reload!" do + + it "should unload the active admin app" do + mock_app.should_receive(:unload!) + reloader.reload! + end + + it "should reload the rails app routes" do + rails_app.should_receive(:reload_routes!) + reloader.reload! + end + + it 'should reset the files within the file_update_checker' do + reloader.file_update_checker.paths.should_receive(:clear) + reloader.file_update_checker.paths.should_receive(:<<).with("app/admin") + reloader.reload! + end + end - it "should use ActionDispatch::Callbacks if rails 3.0" do - reloader = ActiveAdmin::Reloader.new '3.0.0' - reloader.reloader_class.should == ActionDispatch::Callbacks + describe "#watched_paths" do + let(:mock_app){ ActiveAdmin::Application.new } + let(:admin_path){ File.join(Rails.root, "app", "admin") } + + before do + mock_app.load_paths = [admin_path] + end + + it "should return the load path directories" do + reloader.watched_paths.should include(admin_path) + end + + it "should include all files in the directory" do + root = Rails.root + "/app/admin" + reloader.watched_paths.should include(*Dir["#{admin_path}/**/*.rb"]) + end + end + + end