aboutsummaryrefslogtreecommitdiffstats
path: root/railties/lib/rails/generators
diff options
context:
space:
mode:
Diffstat (limited to 'railties/lib/rails/generators')
-rw-r--r--railties/lib/rails/generators/base.rb62
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb14
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb2
-rw-r--r--railties/lib/rails/generators/test_case.rb239
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb4
5 files changed, 286 insertions, 35 deletions
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index 226ae63963..5e8c2730fd 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -76,17 +76,18 @@ module Rails
#
# The controller generator will then try to invoke the following generators:
#
- # "rails:generators:test_unit", "test_unit:generators:controller", "test_unit"
+ # "rails:test_unit", "test_unit:controller", "test_unit"
#
- # In this case, the "test_unit:generators:controller" is available and is
- # invoked. This allows any test framework to hook into Rails as long as it
- # provides any of the hooks above.
+ # Notice that "rails:generators:test_unit" could be loaded as well, what
+ # Rails looks for is the first and last parts of the namespace. This is what
+ # allows any test framework to hook into Rails as long as it provides any
+ # of the hooks above.
#
# ==== Options
#
- # This lookup can be customized with two options: :base and :as. The first
- # is the root module value and in the example above defaults to "rails".
- # The later defaults to the generator name, without the "Generator" ending.
+ # The first and last part used to find the generator to be invoked are
+ # guessed based on class invokes hook_for, as noticed in the example above.
+ # This can be customized with two options: :base and :as.
#
# Let's suppose you are creating a generator that needs to invoke the
# controller generator from test unit. Your first attempt is:
@@ -97,7 +98,7 @@ module Rails
#
# The lookup in this case for test_unit as input is:
#
- # "test_unit:generators:awesome", "test_unit"
+ # "test_unit:awesome", "test_unit"
#
# Which is not the desired the lookup. You can change it by providing the
# :as option:
@@ -108,18 +109,18 @@ module Rails
#
# And now it will lookup at:
#
- # "test_unit:generators:awesome", "test_unit"
+ # "test_unit:controller", "test_unit"
#
# Similarly, if you want it to also lookup in the rails namespace, you just
# need to provide the :base value:
#
# class AwesomeGenerator < Rails::Generators::Base
- # hook_for :test_framework, :base => :rails, :as => :controller
+ # hook_for :test_framework, :in => :rails, :as => :controller
# end
#
# And the lookup is exactly the same as previously:
#
- # "rails:generators:test_unit", "test_unit:generators:controller", "test_unit"
+ # "rails:test_unit", "test_unit:controller", "test_unit"
#
# ==== Switches
#
@@ -151,11 +152,11 @@ module Rails
# ==== Custom invocations
#
# You can also supply a block to hook_for to customize how the hook is
- # going to be invoked. The block receives two parameters, an instance
+ # going to be invoked. The block receives two arguments, an instance
# of the current class and the klass to be invoked.
#
# For example, in the resource generator, the controller should be invoked
- # with a pluralized class name. By default, it is invoked with the same
+ # with a pluralized class name. But by default it is invoked with the same
# name as the resource generator, which is singular. To change this, we
# can give a block to customize how the controller can be invoked.
#
@@ -178,11 +179,11 @@ module Rails
end
unless class_options.key?(name)
- class_option name, defaults.merge!(options)
+ class_option(name, defaults.merge!(options))
end
hooks[name] = [ in_base, as_hook ]
- invoke_from_option name, options, &block
+ invoke_from_option(name, options, &block)
end
end
@@ -193,7 +194,7 @@ module Rails
# remove_hook_for :orm
#
def self.remove_hook_for(*names)
- remove_invocation *names
+ remove_invocation(*names)
names.each do |name|
hooks.delete(name)
@@ -219,12 +220,16 @@ module Rails
# and can point to wrong directions when inside an specified directory.
base.source_root
- if base.name && base.name !~ /Base$/ && base.base_name && base.generator_name && defined?(Rails.root) && Rails.root
- path = File.expand_path(File.join(Rails.root, 'lib', 'templates'))
- if base.name.include?('::')
- base.source_paths << File.join(path, base.base_name, base.generator_name)
- else
- base.source_paths << File.join(path, base.generator_name)
+ if base.name && base.name !~ /Base$/
+ Rails::Generators.subclasses << base
+
+ if defined?(Rails.root) && Rails.root
+ path = File.expand_path(File.join(Rails.root, 'lib', 'templates'))
+ if base.name.include?('::')
+ base.source_paths << File.join(path, base.base_name, base.generator_name)
+ else
+ base.source_paths << File.join(path, base.generator_name)
+ end
end
end
end
@@ -267,7 +272,7 @@ module Rails
# parameters.
#
def invoked?(args)
- args.last.is_a?(Hash) && args.last.key?(:invocations)
+ args.last.is_a?(Hash) && (args.last.key?(:invocations) || args.last.key?(:destination_root))
end
# Use Rails default banner.
@@ -290,12 +295,10 @@ module Rails
# Rails::Generators::MetalGenerator will return "metal" as generator name.
#
def self.generator_name
- if name
- @generator_name ||= begin
- if klass_name = name.to_s.split('::').last
- klass_name.sub!(/Generator$/, '')
- klass_name.underscore
- end
+ @generator_name ||= begin
+ if generator = name.to_s.split('::').last
+ generator.sub!(/Generator$/, '')
+ generator.underscore
end
end
end
@@ -339,6 +342,7 @@ module Rails
#
def self.prepare_for_invocation(name, value) #:nodoc:
if value && constants = self.hooks[name]
+ value = name if TrueClass === value
Rails::Generators.find_by_namespace(value, *constants)
else
super
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index ee401b1fde..fc6a3cdee8 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -46,15 +46,16 @@ module Rails::Generators
def initialize(*args)
super
- if !options[:no_activerecord] && !DATABASES.include?(options[:database])
+ if !options[:skip_activerecord] && !DATABASES.include?(options[:database])
raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}."
end
end
def create_root
self.destination_root = File.expand_path(app_path, destination_root)
- empty_directory '.'
+ valid_app_const?
+ empty_directory '.'
set_default_accessors!
FileUtils.cd(destination_root)
end
@@ -193,7 +194,14 @@ module Rails::Generators
end
def app_const
- @app_const ||= "#{app_name.classify}::Application"
+ @app_const ||= "#{app_name.gsub(/\W/, '_').squeeze('_').classify}::Application"
+ end
+
+ def valid_app_const?
+ case app_const
+ when /^\d/
+ raise Error, "Invalid application name #{app_name}. Please give a name which does not start with numbers."
+ end
end
def app_secret
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index b6c1cef8cd..dce5b55d86 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -7,7 +7,7 @@ module <%= app_name.classify %>
# -- all .rb files in that directory are automatically loaded.
# Add additional load paths for your own custom dirs
- # config.load_paths += %W( #{root}/extras )
+ # config.load_paths += %W( #{config.root}/extras )
# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named
diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb
new file mode 100644
index 0000000000..643d7856c5
--- /dev/null
+++ b/railties/lib/rails/generators/test_case.rb
@@ -0,0 +1,239 @@
+require 'active_support/test_case'
+require 'active_support/core_ext/class/inheritable_attributes'
+require 'active_support/core_ext/hash/reverse_merge'
+require 'rails/generators'
+require 'fileutils'
+
+module Rails
+ module Generators
+ # Disable color in output. Easier to debug.
+ no_color!
+
+ # This class provides a TestCase for testing generators. To setup, you need
+ # just to configure the destination and set which generator is being tested:
+ #
+ # class AppGeneratorTest < Rails::Generators::TestCase
+ # tests AppGenerator
+ # destination File.expand_path("../tmp", File.dirname(__FILE__))
+ # end
+ #
+ # If you want to ensure your destination root is clean before running each test,
+ # you can set a setup callback:
+ #
+ # class AppGeneratorTest < Rails::Generators::TestCase
+ # tests AppGenerator
+ # destination File.expand_path("../tmp", File.dirname(__FILE__))
+ # setup :prepare_destination
+ # end
+ #
+ class TestCase < ActiveSupport::TestCase
+ include FileUtils
+
+ extlib_inheritable_accessor :destination_root, :current_path, :generator_class,
+ :default_arguments, :instance_writer => false
+
+ # Generators frequently change the current path using +FileUtils.cd+.
+ # So we need to store the path at file load and revert back to it after each test.
+ self.current_path = File.expand_path(Dir.pwd)
+ self.default_arguments = []
+
+ setup :destination_root_is_set?, :ensure_current_path
+ teardown :ensure_current_path
+
+ # Sets which generator should be tested:
+ #
+ # tests AppGenerator
+ #
+ def self.tests(klass)
+ self.generator_class = klass
+ end
+
+ # Sets default arguments on generator invocation. This can be overwritten when
+ # invoking it.
+ #
+ # arguments %w(app_name --skip-activerecord)
+ #
+ def self.arguments(array)
+ self.default_arguments = array
+ end
+
+ # Sets the destination of generator files:
+ #
+ # destination File.expand_path("../tmp", File.dirname(__FILE__))
+ #
+ def self.destination(path)
+ self.destination_root = path
+ end
+
+ # Captures the given stream and returns it:
+ #
+ # stream = capture(:stdout){ puts "Cool" }
+ # stream #=> "Cool\n"
+ #
+ def capture(stream)
+ begin
+ stream = stream.to_s
+ eval "$#{stream} = StringIO.new"
+ yield
+ result = eval("$#{stream}").string
+ ensure
+ eval("$#{stream} = #{stream.upcase}")
+ end
+
+ result
+ end
+ alias :silence :capture
+
+ # Asserts a given file exists. You need to supply an absolute path or a path relative
+ # to the configured destination:
+ #
+ # assert_file "config/environment.rb"
+ #
+ # You can also give extra arguments. If the argument is a regexp, it will check if the
+ # regular expression matches the given file content. If it's a string, it compares the
+ # file with the given string:
+ #
+ # assert_file "config/environment.rb", /initialize/
+ #
+ # Finally, when a block is given, it yields the file content:
+ #
+ # assert_file "app/controller/products_controller.rb" do |controller|
+ # assert_instance_method :index, content do |index|
+ # assert_match /Product\.all/, index
+ # end
+ # end
+ #
+ def assert_file(relative, *contents)
+ absolute = File.expand_path(relative, destination_root)
+ assert File.exists?(absolute), "Expected file #{relative.inspect} to exist, but does not"
+
+ read = File.read(absolute) if block_given? || !contents.empty?
+ yield read if block_given?
+
+ contents.each do |content|
+ case content
+ when String
+ assert_equal content, read
+ when Regexp
+ assert_match content, read
+ end
+ end
+ end
+ alias :assert_directory :assert_file
+
+ # Asserts a given file does not exist. You need to supply an absolute path or a
+ # path relative to the configured destination:
+ #
+ # assert_no_file "config/random.rb"
+ #
+ def assert_no_file(relative)
+ absolute = File.expand_path(relative, destination_root)
+ assert !File.exists?(absolute), "Expected file #{relative.inspect} to not exist, but does"
+ end
+ alias :assert_no_directory :assert_no_file
+
+ # Asserts a given file does not exist. You need to supply an absolute path or a
+ # path relative to the configured destination:
+ #
+ # assert_migration "db/migrate/create_products.rb"
+ #
+ # This method manipulates the given path and tries to find any migration which
+ # matches the migration name. For example, the call above is converted to:
+ #
+ # assert_file "db/migrate/003_create_products.rb"
+ #
+ # Consequently, assert_migration accepts the same arguments has assert_file.
+ #
+ def assert_migration(relative, *contents, &block)
+ file_name = migration_file_name(relative)
+ assert file_name, "Expected migration #{relative} to exist, but was not found"
+ assert_file file_name, *contents, &block
+ end
+
+ # Asserts a given migration does not exist. You need to supply an absolute path or a
+ # path relative to the configured destination:
+ #
+ # assert_no_file "config/random.rb"
+ #
+ def assert_no_migration(relative)
+ file_name = migration_file_name(relative)
+ assert_nil file_name, "Expected migration #{relative} to not exist, but found #{file_name}"
+ end
+
+ # Asserts the given class method exists in the given content. This method does not detect
+ # class methods inside (class << self), only class methods which starts with "self.".
+ # When a block is given, it yields the content of the method.
+ #
+ # assert_migration "db/migrate/create_products.rb" do |migration|
+ # assert_class_method :up, migration do |up|
+ # assert_match /create_table/, up
+ # end
+ # end
+ #
+ def assert_class_method(method, content, &block)
+ assert_instance_method "self.#{method}", content, &block
+ end
+
+ # Asserts the given method exists in the given content. When a block is given,
+ # it yields the content of the method.
+ #
+ # assert_file "app/controller/products_controller.rb" do |controller|
+ # assert_instance_method :index, content do |index|
+ # assert_match /Product\.all/, index
+ # end
+ # end
+ #
+ def assert_instance_method(method, content)
+ assert content =~ /def #{method}(\(.+\))?(.*?)\n end/m, "Expected to have method #{method}"
+ yield $2.strip if block_given?
+ end
+ alias :assert_method :assert_instance_method
+
+ # Runs the generator configured for this class. The first argument is an array like
+ # command line arguments:
+ #
+ # class AppGeneratorTest < Rails::Generators::TestCase
+ # tests AppGenerator
+ # destination File.expand_path("../tmp", File.dirname(__FILE__))
+ # teardown :cleanup_destination_root
+ #
+ # test "database.yml is not created when skipping activerecord" do
+ # run_generator %w(myapp --skip-activerecord)
+ # assert_no_file "config/database.yml"
+ # end
+ # end
+ #
+ # You can provide a configuration hash as second argument. This method returns the output
+ # printed by the generator.
+ def run_generator(args=self.default_arguments, config={})
+ capture(:stdout) { self.generator_class.start(args, config.reverse_merge(:destination_root => destination_root)) }
+ end
+
+ # Instantiate the generator.
+ def generator(args=self.default_arguments, options={}, config={})
+ @generator ||= self.generator_class.new(args, options, config.reverse_merge(:destination_root => destination_root))
+ end
+
+ protected
+
+ def destination_root_is_set? #:nodoc:
+ raise "You need to configure your Rails::Generators::TestCase destination root." unless destination_root
+ end
+
+ def ensure_current_path #:nodoc:
+ cd current_path
+ end
+
+ def prepare_destination
+ rm_rf(destination_root)
+ mkdir_p(destination_root)
+ end
+
+ def migration_file_name(relative) #:nodoc:
+ absolute = File.expand_path(relative, destination_root)
+ dirname, file_name = File.dirname(absolute), File.basename(absolute).sub(/\.rb$/, '')
+ Dir.glob("#{dirname}/[0-9]*_*.rb").grep(/\d+_#{file_name}.rb$/).first
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
index e4bf4035da..9380aa49b6 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
@@ -16,7 +16,7 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase
test "should create <%= file_name %>" do
assert_difference('<%= class_name %>.count') do
- post :create, :<%= file_name %> => { }
+ post :create, :<%= file_name %> => <%= table_name %>(:one).attributes
end
assert_redirected_to <%= file_name %>_path(assigns(:<%= file_name %>))
@@ -33,7 +33,7 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase
end
test "should update <%= file_name %>" do
- put :update, :id => <%= table_name %>(:one).to_param, :<%= file_name %> => { }
+ put :update, :id => <%= table_name %>(:one).to_param, :<%= file_name %> => <%= table_name %>(:one).attributes
assert_redirected_to <%= file_name %>_path(assigns(:<%= file_name %>))
end