Moved support from both Action Pack and Active Record into a separate module called Active Support that can be included using svn:externals in both

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@273 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
David Heinemeier Hansson 2004-12-29 21:03:21 +00:00
parent 1b0da48fe9
commit bd323b3c99
11 changed files with 484 additions and 0 deletions

@ -0,0 +1,57 @@
# Extends the class object with class and instance accessors for class attributes,
# just like the native attr* accessors for instance attributes.
class Class # :nodoc:
def cattr_reader(*syms)
syms.each do |sym|
class_eval <<-EOS
if ! defined? @@#{sym.id2name}
@@#{sym.id2name} = nil
end
def self.#{sym.id2name}
@@#{sym}
end
def #{sym.id2name}
@@#{sym}
end
def call_#{sym.id2name}
case @@#{sym.id2name}
when Symbol then send(@@#{sym})
when Proc then @@#{sym}.call(self)
when String then @@#{sym}
else nil
end
end
EOS
end
end
def cattr_writer(*syms)
syms.each do |sym|
class_eval <<-EOS
if ! defined? @@#{sym.id2name}
@@#{sym.id2name} = nil
end
def self.#{sym.id2name}=(obj)
@@#{sym.id2name} = obj
end
def self.set_#{sym.id2name}(obj)
@@#{sym.id2name} = obj
end
def #{sym.id2name}=(obj)
@@#{sym} = obj
end
EOS
end
end
def cattr_accessor(*syms)
cattr_reader(*syms)
cattr_writer(*syms)
end
end

@ -0,0 +1,41 @@
# Allows attributes to be shared within an inheritance hierarchy, but where each descendant gets a copy of
# their parents' attributes, instead of just a pointer to the same. This means that the child can add elements
# to, for example, an array without those additions being shared with either their parent, siblings, or
# children, which is unlike the regular class-level attributes that are shared across the entire hierarchy.
module ClassInheritableAttributes # :nodoc:
def self.append_features(base)
super
base.extend(ClassMethods)
end
module ClassMethods # :nodoc:
@@classes ||= {}
def inheritable_attributes
@@classes[self] ||= {}
end
def write_inheritable_attribute(key, value)
inheritable_attributes[key] = value
end
def write_inheritable_array(key, elements)
write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
end
def read_inheritable_attribute(key)
inheritable_attributes[key]
end
def reset_inheritable_attributes
inheritable_attributes.clear
end
private
def inherited(child)
@@classes[child] = inheritable_attributes.dup
end
end
end

@ -0,0 +1,10 @@
require 'logger'
class Logger #:nodoc:
private
remove_const "Format"
Format = "%s\n"
def format_message(severity, timestamp, msg, progname)
Format % [msg]
end
end

@ -0,0 +1,65 @@
require 'action_controller/support/module_attribute_accessors'
module Dependencies
extend self
@@loaded = [ ]
mattr_accessor :loaded
@@mechanism = :load
mattr_accessor :mechanism
def depend_on(file_name, swallow_load_errors = false)
begin
loaded << require_or_load(file_name) if !loaded.include?(file_name)
rescue LoadError
raise unless swallow_load_errors
end
end
def associate_with(file_name)
depend_on(file_name, true)
end
def reload
loaded.each do |file_name|
begin
silence_warnings { load("#{file_name}.rb") }
rescue LoadError
# The association didn't reside in its own file, so we assume it was required by other means
end
end
end
def clear
self.loaded = [ ]
end
private
def require_or_load(file_name)
mechanism == :load ? silence_warnings { load("#{file_name}.rb") } : require(file_name)
return file_name
end
end
Object.send(:define_method, :require_dependency) { |file_name| Dependencies.depend_on(file_name) } unless Object.respond_to?(:require_dependency)
Object.send(:define_method, :require_association) { |file_name| Dependencies.associate_with(file_name) } unless Object.respond_to?(:require_association)
class Object
class << self
# Use const_missing to autoload associations so we don't have to
# require_association when using single-table inheritance.
unless respond_to?(:pre_dependency_const_missing)
alias_method :pre_dependency_const_missing, :const_missing
def const_missing(class_id)
begin
require_dependency(Inflector.underscore(Inflector.demodulize(class_id.to_s)))
return Object.const_get(class_id) if Object.const_defined?(class_id)
rescue LoadError
pre_dependency_const_missing(class_id)
end
end
end
end
end

@ -0,0 +1,80 @@
# The Inflector transforms words from singular to plural, class names to table names, modulized class names to ones without,
# and class names to foreign keys.
module Inflector
extend self
def pluralize(word)
result = word.dup
plural_rules.each do |(rule, replacement)|
break if result.gsub!(rule, replacement)
end
return result
end
def singularize(word)
result = word.dup
singular_rules.each do |(rule, replacement)|
break if result.gsub!(rule, replacement)
end
return result
end
def camelize(lower_case_and_underscored_word)
lower_case_and_underscored_word.gsub(/(^|_)(.)/){$2.upcase}
end
def underscore(camel_cased_word)
camel_cased_word.gsub(/([A-Z]+)([A-Z])/,'\1_\2').gsub(/([a-z])([A-Z])/,'\1_\2').downcase
end
def demodulize(class_name_in_module)
class_name_in_module.gsub(/^.*::/, '')
end
def tableize(class_name)
pluralize(underscore(class_name))
end
def classify(table_name)
camelize(singularize(table_name))
end
def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
Inflector.underscore(Inflector.demodulize(class_name)) +
(separate_class_name_and_id_with_underscore ? "_id" : "id")
end
private
def plural_rules #:doc:
[
[/(x|ch|ss)$/, '\1es'], # search, switch, fix, box, process, address
[/([^aeiouy]|qu)ies$/, '\1y'],
[/([^aeiouy]|qu)y$/, '\1ies'], # query, ability, agency
[/(?:([^f])fe|([lr])f)$/, '\1\2ves'], # half, safe, wife
[/sis$/, 'ses'], # basis, diagnosis
[/([ti])um$/, '\1a'], # datum, medium
[/person$/, 'people'], # person, salesperson
[/man$/, 'men'], # man, woman, spokesman
[/child$/, 'children'], # child
[/s$/, 's'], # no change (compatibility)
[/$/, 's']
]
end
def singular_rules #:doc:
[
[/(x|ch|ss)es$/, '\1'],
[/movies$/, 'movie'],
[/([^aeiouy]|qu)ies$/, '\1y'],
[/([lr])ves$/, '\1f'],
[/([^f])ves$/, '\1fe'],
[/(analy|ba|diagno|parenthe|progno|synop|the)ses$/, '\1sis'],
[/([ti])a$/, '\1um'],
[/people$/, 'person'],
[/men$/, 'man'],
[/status$/, 'status'],
[/children$/, 'child'],
[/s$/, '']
]
end
end

@ -0,0 +1,6 @@
def silence_warnings
old_verbose, $VERBOSE = $VERBOSE, nil
result = yield
$VERBOSE = old_verbose
return result
end

@ -0,0 +1,57 @@
# Extends the module object with module and instance accessors for class attributes,
# just like the native attr* accessors for instance attributes.
class Module # :nodoc:
def mattr_reader(*syms)
syms.each do |sym|
class_eval <<-EOS
if ! defined? @@#{sym.id2name}
@@#{sym.id2name} = nil
end
def self.#{sym.id2name}
@@#{sym}
end
def #{sym.id2name}
@@#{sym}
end
def call_#{sym.id2name}
case @@#{sym.id2name}
when Symbol then send(@@#{sym})
when Proc then @@#{sym}.call(self)
when String then @@#{sym}
else nil
end
end
EOS
end
end
def mattr_writer(*syms)
syms.each do |sym|
class_eval <<-EOS
if ! defined? @@#{sym.id2name}
@@#{sym.id2name} = nil
end
def self.#{sym.id2name}=(obj)
@@#{sym.id2name} = obj
end
def self.set_#{sym.id2name}(obj)
@@#{sym.id2name} = obj
end
def #{sym.id2name}=(obj)
@@#{sym} = obj
end
EOS
end
end
def mattr_accessor(*syms)
mattr_reader(*syms)
mattr_writer(*syms)
end
end

@ -0,0 +1,5 @@
$loaded_service_one ||= 0
$loaded_service_one += 1
class ServiceOne
end

@ -0,0 +1,2 @@
class ServiceTwo
end

@ -0,0 +1,39 @@
require File.dirname(__FILE__) + '/../abstract_unit'
require 'action_controller/support/dependencies'
$LOAD_PATH << File.dirname(__FILE__) + '/../fixtures/dependencies'
class DependenciesTest < Test::Unit::TestCase
def teardown
Dependencies.clear
end
def test_require_dependency
require_dependency("service_one")
require_dependency("service_two")
assert_equal 2, Dependencies.loaded.size
end
def test_require_dependency_two_times
require_dependency("service_one")
require_dependency("service_one")
assert_equal 1, Dependencies.loaded.size
end
def test_reloading_dependency
require_dependency("service_one")
require_dependency("service_one")
assert_equal 1, $loaded_service_one
Dependencies.reload
assert_equal 2, $loaded_service_one
end
def test_require_missing_dependency
assert_raises(LoadError) { require_dependency("missing_service") }
end
def test_require_missing_association
assert_nothing_raised { require_association("missing_model") }
end
end

@ -0,0 +1,122 @@
require 'abstract_unit'
class InflectorTest < Test::Unit::TestCase
SingularToPlural = {
"search" => "searches",
"switch" => "switches",
"fix" => "fixes",
"box" => "boxes",
"process" => "processes",
"address" => "addresses",
"case" => "cases",
"stack" => "stacks",
"category" => "categories",
"query" => "queries",
"ability" => "abilities",
"agency" => "agencies",
"movie" => "movies",
"wife" => "wives",
"safe" => "saves",
"half" => "halves",
"salesperson" => "salespeople",
"person" => "people",
"spokesman" => "spokesmen",
"man" => "men",
"woman" => "women",
"basis" => "bases",
"diagnosis" => "diagnoses",
"datum" => "data",
"medium" => "media",
"node_child" => "node_children",
"child" => "children",
"experience" => "experiences",
"day" => "days",
"comment" => "comments",
"foobar" => "foobars"
}
CamelToUnderscore = {
"Product" => "product",
"SpecialGuest" => "special_guest",
"ApplicationController" => "application_controller"
}
ClassNameToForeignKeyWithUnderscore = {
"Person" => "person_id",
"MyApplication::Billing::Account" => "account_id"
}
ClassNameToForeignKeyWithoutUnderscore = {
"Person" => "personid",
"MyApplication::Billing::Account" => "accountid"
}
ClassNameToTableName = {
"PrimarySpokesman" => "primary_spokesmen",
"NodeChild" => "node_children"
}
def test_pluralize
SingularToPlural.each do |singular, plural|
assert_equal(plural, Inflector.pluralize(singular))
end
assert_equal("plurals", Inflector.pluralize("plurals"))
end
def test_singularize
SingularToPlural.each do |singular, plural|
assert_equal(singular, Inflector.singularize(plural))
end
end
def test_camelize
CamelToUnderscore.each do |camel, underscore|
assert_equal(camel, Inflector.camelize(underscore))
end
end
def test_underscore
CamelToUnderscore.each do |camel, underscore|
assert_equal(underscore, Inflector.underscore(camel))
end
assert_equal "html_tidy", Inflector.underscore("HTMLTidy")
assert_equal "html_tidy_generator", Inflector.underscore("HTMLTidyGenerator")
end
def test_demodulize
assert_equal "Account", Inflector.demodulize("MyApplication::Billing::Account")
end
def test_foreign_key
ClassNameToForeignKeyWithUnderscore.each do |klass, foreign_key|
assert_equal(foreign_key, Inflector.foreign_key(klass))
end
ClassNameToForeignKeyWithoutUnderscore.each do |klass, foreign_key|
assert_equal(foreign_key, Inflector.foreign_key(klass, false))
end
end
def test_tableize
ClassNameToTableName.each do |class_name, table_name|
assert_equal(table_name, Inflector.tableize(class_name))
end
end
def test_classify
ClassNameToTableName.each do |class_name, table_name|
assert_equal(class_name, Inflector.classify(table_name))
end
end
end