From c844755e5a0c3d4edfcc78f9c30ef91fa0de550a Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 15 Feb 2005 00:51:02 +0000 Subject: Merged back the Routing branch git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@614 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- activesupport/CHANGELOG | 4 ++ .../lib/core_ext/hash/indifferent_access.rb | 10 ++-- activesupport/lib/core_ext/string/inflections.rb | 4 ++ activesupport/lib/dependencies.rb | 67 +++++++++++++++++++++- activesupport/lib/inflector.rb | 12 +++- activesupport/test/inflector_test.rb | 31 ++++++++++ .../test/loading_module/admin/access_controller.rb | 2 + .../test/loading_module/admin/user_controller.rb | 2 + .../test/loading_module/content_controller.rb | 2 + .../test/loading_module/resource_controller.rb | 2 + activesupport/test/loading_module_tests.rb | 63 ++++++++++++++++++++ 11 files changed, 190 insertions(+), 9 deletions(-) create mode 100644 activesupport/test/loading_module/admin/access_controller.rb create mode 100644 activesupport/test/loading_module/admin/user_controller.rb create mode 100644 activesupport/test/loading_module/content_controller.rb create mode 100644 activesupport/test/loading_module/resource_controller.rb create mode 100644 activesupport/test/loading_module_tests.rb (limited to 'activesupport') diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 7a005ce970..cf9fc0a728 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -2,6 +2,10 @@ * Added IndifferentAccess as a way to wrap a hash by a symbol-based store that also can be accessed by string keys +* Added Inflector.constantize to turn "Admin::User" into a reference for the constant Admin::User + +* Added that Inflector.camelize and Inflector.underscore can deal with modules like turning "Admin::User" into "admin/user" and back + * Added Inflector.humanize to turn attribute names like employee_salary into "Employee salary". Used by automated error reporting in AR. * Added availability of class inheritable attributes to the masses #477 [bitsweat] diff --git a/activesupport/lib/core_ext/hash/indifferent_access.rb b/activesupport/lib/core_ext/hash/indifferent_access.rb index 3fe0999866..2353cfaf3b 100644 --- a/activesupport/lib/core_ext/hash/indifferent_access.rb +++ b/activesupport/lib/core_ext/hash/indifferent_access.rb @@ -8,17 +8,17 @@ class HashWithIndifferentAccess < Hash end end - alias_method :regular_read, :[] + alias_method :regular_reader, :[] unless method_defined?(:regular_reader) def [](key) case key - when Symbol: regular_read(key) || regular_read(key.to_s) - when String: regular_read(key) || regular_read(key.to_sym) - else regular_read(key) + when Symbol: regular_reader(key) || regular_reader(key.to_s) + when String: regular_reader(key) || regular_reader(key.to_sym) + else regular_reader(key) end end - alias_method :regular_writer, :[]= + alias_method :regular_writer, :[]= unless method_defined?(:regular_writer) def []=(key, value) regular_writer(key.is_a?(String) ? key.to_sym : key, value) diff --git a/activesupport/lib/core_ext/string/inflections.rb b/activesupport/lib/core_ext/string/inflections.rb index 5d1070b00a..aa4ff3a74d 100644 --- a/activesupport/lib/core_ext/string/inflections.rb +++ b/activesupport/lib/core_ext/string/inflections.rb @@ -39,6 +39,10 @@ module ActiveSupport #:nodoc: def foreign_key(separate_class_name_and_id_with_underscore = true) Inflector.foreign_key(self, separate_class_name_and_id_with_underscore) end + + def constantize + Inflector.constantize(self) + end end end end diff --git a/activesupport/lib/dependencies.rb b/activesupport/lib/dependencies.rb index 7f53998e7a..0a9b8e1d3d 100644 --- a/activesupport/lib/dependencies.rb +++ b/activesupport/lib/dependencies.rb @@ -2,7 +2,7 @@ require File.dirname(__FILE__) + '/module_attribute_accessors' module Dependencies extend self - + @@loaded = [ ] mattr_accessor :loaded @@ -41,6 +41,68 @@ module Dependencies def remove_subclasses_for(*classes) classes.each { |klass| klass.remove_subclasses } end + + # LoadingModules implement namespace-safe dynamic loading. + # They support automatic loading via const_missing, allowing contained items to be automatically + # loaded when required. No extra syntax is required, as expressions such as Controller::Admin::UserController + # load the relavent files automatically. + # + # Ruby-style modules are supported, as a folder named 'submodule' will load 'submodule.rb' when available. + class LoadingModule < Module + attr_reader :path + + def initialize(filesystem_root, path=[]) + @path = path + @filesystem_root = filesystem_root + end + + # The path to this module in the filesystem. + # Any subpath provided is taken to be composed of filesystem names. + def filesystem_path(subpath=[]) + File.join(@filesystem_root, self.path, subpath) + end + + # Load missing constants if possible. + def const_missing(name) + return const_get(name) if const_defined?(name) == false && const_load!(name) + super(name) + end + + # Load the controller class or a parent module. + def const_load!(name) + name = name.to_s if name.kind_of? Symbol + + if File.directory? filesystem_path(name.underscore) + # Is it a submodule? If so, create a new LoadingModule *before* loading it. + # This ensures that subitems will be loadable + new_module = LoadingModule.new(@filesystem_root, self.path + [name.underscore]) + const_set(name, new_module) + Object.const_set(name, new_module) if @path.empty? + end + + source_file = filesystem_path("#{(name == 'ApplicationController' ? 'Application' : name).underscore}.rb") + self.load_file(source_file) if File.file?(source_file) + self.const_defined?(name.camelize) + end + + # Is this name present or loadable? + # This method is used by Routes to find valid controllers. + def const_available?(name) + name = name.to_s unless name.kind_of? String + File.directory?(filesystem_path(name.underscore)) || File.file?(filesystem_path("#{name.underscore}.rb")) + end + + def clear + constants.each do |name| + Object.send(:remove_const, name) if Object.const_defined?(name) && @path.empty? + self.send(:remove_const, name) + end + end + + def load_file(file_path) + Controllers.module_eval(IO.read(file_path), file_path, 1) # Hard coded Controller line here!!! + end + end end Object.send(:define_method, :require_or_load) { |file_name| Dependencies.require_or_load(file_name) } unless Object.respond_to?(:require_or_load) @@ -52,6 +114,9 @@ class Object #:nodoc: # Use const_missing to autoload associations so we don't have to # require_association when using single-table inheritance. def const_missing(class_id) + if Object.const_defined?(:Controllers) and Object::Controllers.const_available?(class_id) + return Object::Controllers.const_get(class_id) + end begin require_or_load(class_id.to_s.demodulize.underscore) if Object.const_defined?(class_id) then return Object.const_get(class_id) else raise LoadError end diff --git a/activesupport/lib/inflector.rb b/activesupport/lib/inflector.rb index 065fad3762..a9b2f87be4 100644 --- a/activesupport/lib/inflector.rb +++ b/activesupport/lib/inflector.rb @@ -20,11 +20,11 @@ module Inflector end def camelize(lower_case_and_underscored_word) - lower_case_and_underscored_word.to_s.gsub(/(^|_)(.)/){$2.upcase} + lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase } end def underscore(camel_cased_word) - camel_cased_word.to_s.gsub(/([A-Z]+)([A-Z])/,'\1_\2').gsub(/([a-z])([A-Z])/,'\1_\2').downcase + camel_cased_word.to_s.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z])/,'\1_\2').gsub(/([a-z])([A-Z])/,'\1_\2').downcase end def humanize(lower_case_and_underscored_word) @@ -47,7 +47,13 @@ module Inflector Inflector.underscore(Inflector.demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id") end - + + def constantize(camel_cased_word) + camel_cased_word.split("::").inject(Object) do |final_type, part| + final_type = final_type.const_get(part) + end + end + private def plural_rules #:doc: [ diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 4523430ebe..cdab0f9ed0 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -1,6 +1,13 @@ require 'test/unit' require File.dirname(__FILE__) + '/../lib/inflector' +module A + module B + class C + end + end +end + class InflectorTest < Test::Unit::TestCase SingularToPlural = { "search" => "searches", @@ -50,6 +57,12 @@ class InflectorTest < Test::Unit::TestCase "SpecialGuest" => "special_guest", "ApplicationController" => "application_controller" } + + CamelWithModuleToUnderscoreWithSlash = { + "Admin::Product" => "admin/product", + "Users::Commission::Department" => "users/commission/department", + "UsersSection::CommissionDepartment" => "users_section/commission_department", + } ClassNameToForeignKeyWithUnderscore = { "Person" => "person_id", @@ -100,6 +113,18 @@ class InflectorTest < Test::Unit::TestCase assert_equal "html_tidy_generator", Inflector.underscore("HTMLTidyGenerator") end + def test_camelize_with_module + CamelWithModuleToUnderscoreWithSlash.each do |camel, underscore| + assert_equal(camel, Inflector.camelize(underscore)) + end + end + + def test_underscore_with_slashes + CamelWithModuleToUnderscoreWithSlash.each do |camel, underscore| + assert_equal(underscore, Inflector.underscore(camel)) + end + end + def test_demodulize assert_equal "Account", Inflector.demodulize("MyApplication::Billing::Account") end @@ -131,4 +156,10 @@ class InflectorTest < Test::Unit::TestCase assert_equal(human, Inflector.humanize(underscore)) end end + + def test_constantize + assert_equal A::B::C, Inflector.constantize("A::B::C") + assert_equal InflectorTest, Inflector.constantize("InflectorTest") + assert_raises(NameError) { Inflector.constantize("UnknownClass") } + end end \ No newline at end of file diff --git a/activesupport/test/loading_module/admin/access_controller.rb b/activesupport/test/loading_module/admin/access_controller.rb new file mode 100644 index 0000000000..ddcbda8132 --- /dev/null +++ b/activesupport/test/loading_module/admin/access_controller.rb @@ -0,0 +1,2 @@ +class Admin::AccessController +end diff --git a/activesupport/test/loading_module/admin/user_controller.rb b/activesupport/test/loading_module/admin/user_controller.rb new file mode 100644 index 0000000000..f265f1597a --- /dev/null +++ b/activesupport/test/loading_module/admin/user_controller.rb @@ -0,0 +1,2 @@ +class Admin::UserController +end diff --git a/activesupport/test/loading_module/content_controller.rb b/activesupport/test/loading_module/content_controller.rb new file mode 100644 index 0000000000..f0870161e5 --- /dev/null +++ b/activesupport/test/loading_module/content_controller.rb @@ -0,0 +1,2 @@ +class ContentController +end diff --git a/activesupport/test/loading_module/resource_controller.rb b/activesupport/test/loading_module/resource_controller.rb new file mode 100644 index 0000000000..d948f366bf --- /dev/null +++ b/activesupport/test/loading_module/resource_controller.rb @@ -0,0 +1,2 @@ +class ResourceController +end diff --git a/activesupport/test/loading_module_tests.rb b/activesupport/test/loading_module_tests.rb new file mode 100644 index 0000000000..c1d8c7de62 --- /dev/null +++ b/activesupport/test/loading_module_tests.rb @@ -0,0 +1,63 @@ +require 'test/unit' +require '../lib/core_ext.rb' +require '../lib/dependencies.rb' + +STAGING_DIRECTORY = 'loading_module' + +class LoadingModuleTests < Test::Unit::TestCase + def setup + @loading_module = Dependencies::LoadingModule.new(STAGING_DIRECTORY) + Object.const_set(:Controllers, @loading_module) + end + def teardown + @loading_module.clear + Object.send :remove_const, :Controllers + end + + def test_setup + assert_kind_of Dependencies::LoadingModule, @loading_module + end + + def test_const_available + assert @loading_module.const_available?(:Admin) + assert @loading_module.const_available?(:ResourceController) + assert @loading_module.const_available?(:ContentController) + assert @loading_module.const_available?("ContentController") + + assert_equal false, @loading_module.const_available?(:AdminController) + assert_equal false, @loading_module.const_available?(:RandomName) + end + + def test_const_load_module + assert @loading_module.const_load!(:Admin) + assert_kind_of Module, @loading_module::Admin + assert_kind_of Dependencies::LoadingModule, @loading_module::Admin + end + + def test_const_load_controller + assert @loading_module.const_load!(:ContentController) + assert_kind_of Class, @loading_module::ContentController + end + + def test_const_load_nested_controller + assert @loading_module.const_load!(:Admin) + assert @loading_module::Admin.const_available?(:UserController) + assert @loading_module::Admin.const_load!(:UserController) + assert_kind_of Class, @loading_module::Admin::UserController + end + + def test_pretty_access + assert_kind_of Module, @loading_module::Admin + assert_kind_of Dependencies::LoadingModule, @loading_module::Admin + + assert_kind_of Class, @loading_module::Admin::UserController + assert_kind_of Class, @loading_module::Admin::AccessController + assert_kind_of Class, @loading_module::ResourceController + assert_kind_of Class, @loading_module::ContentController + end + + def test_missing_name + assert_raises(NameError) {@loading_module::PersonController} + assert_raises(NameError) {@loading_module::Admin::FishController} + end +end \ No newline at end of file -- cgit v1.2.3