diff options
author | David Heinemeier Hansson <david@loudthinking.com> | 2004-12-07 11:49:38 +0000 |
---|---|---|
committer | David Heinemeier Hansson <david@loudthinking.com> | 2004-12-07 11:49:38 +0000 |
commit | 2594581e9f5594b32918326be895b4d443ab3e9c (patch) | |
tree | 051f52e4619b70f4757dbabf362d51a7d5f5fde9 | |
parent | 3ee4357b8643c611bbe9eb3a7ce820a5e32cddaa (diff) | |
download | rails-2594581e9f5594b32918326be895b4d443ab3e9c.tar.gz rails-2594581e9f5594b32918326be895b4d443ab3e9c.tar.bz2 rails-2594581e9f5594b32918326be895b4d443ab3e9c.zip |
Added a better generator for scaffolding that actually creates the code, so it can be edited bit by bit. See "script/generate scaffold" [bitsweat]. Added a whole new approach to generators that used the shared "script/generate" command. Run with no-args to see help [bitsweat].
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@63 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
36 files changed, 706 insertions, 112 deletions
diff --git a/railties/Rakefile b/railties/Rakefile index 92a1bfc765..5a286bcd1e 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -21,7 +21,8 @@ TEST_DIRS = %w( fixtures unit functional mocks mocks/development mocks/testing LOG_FILES = %w( apache.log development.log test.log production.log ) HTML_FILES = %w( 404.html 500.html index.html ) -SCRIPT_FILES = %w( new_controller new_model new_mailer new_crud ) +SCRIPT_FILES = %w( generate ) +GENERATORS = %w( controller mailer model scaffold ) VENDOR_LIBS = %w( actionpack activerecord actionmailer railties ) @@ -136,11 +137,16 @@ task :copy_configs do end task :copy_generators do + mkdir_p File.join(PKG_DESTINATION, 'script') SCRIPT_FILES.each do |file| dest_file = File.join(PKG_DESTINATION, 'script', file) cp File.join('generators', "#{file}.rb"), dest_file chmod 0755, dest_file end + + GENERATORS.each do |dir| + cp_r File.join('generators', dir), File.join(PKG_DESTINATION, 'script', dir) + end end task :copy_rootfiles do diff --git a/railties/generators/new_controller.rb b/railties/generators/controller/USAGE index 3060c06382..0259b3d027 100755..100644 --- a/railties/generators/new_controller.rb +++ b/railties/generators/controller/USAGE @@ -1,15 +1,3 @@ -#!/usr/local/bin/ruby -require File.dirname(__FILE__) + '/../config/environment' -require 'generator' - -unless ARGV.empty? - rails_root = File.dirname(__FILE__) + '/..' - name = ARGV.shift - actions = ARGV - Generator::Controller.new(rails_root, name, actions).generate -else - puts <<-END_HELP - NAME new_controller - create controller and view stub files @@ -39,5 +27,3 @@ EXAMPLE The BlogController class will have the following methods: list, display, new, edit. Each will default to render the associated template file. -END_HELP -end diff --git a/railties/generators/controller/controller_generator.rb b/railties/generators/controller/controller_generator.rb new file mode 100644 index 0000000000..4b53741565 --- /dev/null +++ b/railties/generators/controller/controller_generator.rb @@ -0,0 +1,22 @@ +require 'rails_generator' + +class ControllerGenerator < Rails::Generator::Base + attr_reader :actions + + def generate + @actions = args + + # Controller class, functional test, and helper class. + template "controller.rb", "app/controllers/#{file_name}_controller.rb" + template "functional_test.rb", "test/functional/#{file_name}_controller_test.rb" + template "helper.rb", "app/helpers/#{file_name}_helper.rb" + + # Create the views directory even if there are no actions. + FileUtils.mkdir_p "app/views/#{file_name}" + + # Create a view for each action. + actions.each do |action| + template "view.rhtml", "app/views/#{file_name}/#{action}.rhtml", binding + end + end +end diff --git a/railties/generators/controller/templates/controller.rb b/railties/generators/controller/templates/controller.rb new file mode 100644 index 0000000000..f9800ab556 --- /dev/null +++ b/railties/generators/controller/templates/controller.rb @@ -0,0 +1,10 @@ +class <%= class_name %>Controller < AbstractApplicationController +<% if options[:scaffold] -%> + scaffold :<%= singular_name %> +<% end -%> +<% for action in actions -%> + + def <%= action %> + end +<% end -%> +end diff --git a/railties/generators/templates/controller_test.erb b/railties/generators/controller/templates/functional_test.rb index 5577379c62..c975cb3ce3 100644 --- a/railties/generators/templates/controller_test.erb +++ b/railties/generators/controller/templates/functional_test.rb @@ -10,7 +10,7 @@ class <%= class_name %>ControllerTest < Test::Unit::TestCase @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new end - # Replace this with your real tests + # Replace this with your real tests. def test_truth assert true end diff --git a/railties/generators/templates/helper.erb b/railties/generators/controller/templates/helper.rb index 3fe2ecdc74..3fe2ecdc74 100644 --- a/railties/generators/templates/helper.erb +++ b/railties/generators/controller/templates/helper.rb diff --git a/railties/generators/controller/templates/view.rhtml b/railties/generators/controller/templates/view.rhtml new file mode 100644 index 0000000000..7e7a7d53ce --- /dev/null +++ b/railties/generators/controller/templates/view.rhtml @@ -0,0 +1,2 @@ +<h1><%= class_name %>#<%= action %></h1> +<p>Find me in app/views/<%= file_name %>/<%= action %>.rhtml</p> diff --git a/railties/generators/generate.rb b/railties/generators/generate.rb new file mode 100755 index 0000000000..8f2f1497d6 --- /dev/null +++ b/railties/generators/generate.rb @@ -0,0 +1,41 @@ +#!/usr/local/bin/ruby +require File.dirname(__FILE__) + '/../config/environment' +require 'rails_generator' + +unless ARGV.empty? + begin + name = ARGV.shift + Rails::Generator.instance(name, ARGV).generate + rescue Rails::Generator::UsageError => e + puts e.message + end +else + builtin_generators = Rails::Generator.builtin_generators.join(', ') + contrib_generators = Rails::Generator.contrib_generators.join(', ') + + $stderr.puts <<end_usage + #{$0} generator [args] + + Rails comes with #{builtin_generators} generators. + #{$0} controller Login login logout + #{$0} model Account + #{$0} mailer AccountMailer + #{$0} scaffold Account action another_action + +end_usage + + unless contrib_generators.empty? + $stderr.puts " Installed generators (in #{RAILS_ROOT}/generators):" + $stderr.puts " #{contrib_generators}" + $stderr.puts + end + + $stderr.puts <<end_usage + More generators are available at http://rubyonrails.org + 1. Download, for example, auth_controller.zip + 2. Unzip to directory #{RAILS_ROOT}/generators/auth_controller + 3. Generate without args for usage information + #{$0} auth_controller +end_usage + exit 0 +end diff --git a/railties/generators/new_mailer.rb b/railties/generators/mailer/USAGE index 05d0c9ae82..b6f2bbd54b 100644 --- a/railties/generators/new_mailer.rb +++ b/railties/generators/mailer/USAGE @@ -1,15 +1,3 @@ -#!/usr/local/bin/ruby -require File.dirname(__FILE__) + '/../config/environment' -require 'generator' - -unless ARGV.empty? - rails_root = File.dirname(__FILE__) + '/..' - name = ARGV.shift - actions = ARGV - Generator::Mailer.new(rails_root, name, actions).generate -else - puts <<-END_HELP - NAME new_mailer - create mailer and view stub files @@ -39,5 +27,3 @@ EXAMPLE The Notifications class will have the following methods: signup, forgot_password, and invoice. -END_HELP -end diff --git a/railties/generators/mailer/mailer_generator.rb b/railties/generators/mailer/mailer_generator.rb new file mode 100644 index 0000000000..ae0ad6bbe4 --- /dev/null +++ b/railties/generators/mailer/mailer_generator.rb @@ -0,0 +1,22 @@ +require 'rails_generator' + +class MailerGenerator < Rails::Generator::Base + attr_reader :actions + + def generate + @actions = args + + # Mailer class and unit test. + template "mailer.rb", "app/models/#{file_name}.rb" + template "unit_test.rb", "test/unit/#{file_name}_test.rb" + + # Test fixtures directory. + FileUtils.mkdir_p "test/fixtures/#{table_name}" + + # View template and fixture for each action. + args.each do |action| + template "view.rhtml", "app/views/#{file_name}/#{action}.rhtml", binding + template "fixture.rhtml", "test/fixtures/#{table_name}/#{action}", binding + end + end +end diff --git a/railties/generators/templates/mailer_fixture.rhtml b/railties/generators/mailer/templates/fixture.rhtml index f315d430ed..f315d430ed 100644 --- a/railties/generators/templates/mailer_fixture.rhtml +++ b/railties/generators/mailer/templates/fixture.rhtml diff --git a/railties/generators/templates/mailer.erb b/railties/generators/mailer/templates/mailer.rb index 5afc254923..6621ba9a01 100644 --- a/railties/generators/templates/mailer.erb +++ b/railties/generators/mailer/templates/mailer.rb @@ -1,8 +1,8 @@ require 'action_mailer' class <%= class_name %> < ActionMailer::Base - <% for action in actions -%> + def <%= action %>(sent_on = Time.now) @recipients = '' @from = '' @@ -10,6 +10,5 @@ class <%= class_name %> < ActionMailer::Base @body = {} @sent_on = sent_on end - <% end -%> end diff --git a/railties/generators/templates/mailer_test.erb b/railties/generators/mailer/templates/unit_test.rb index f17d614195..3bf460907f 100644 --- a/railties/generators/templates/mailer_test.erb +++ b/railties/generators/mailer/templates/unit_test.rb @@ -12,11 +12,11 @@ class <%= class_name %>Test < Test::Unit::TestCase @expected = TMail::Mail.new @expected.to = 'test@localhost' @expected.from = 'test@localhost' - @expected.subject = '<%= class_name %> test mail' end <% for action in actions -%> def test_<%= action %> + @expected.subject = '<%= class_name %>#<%= action %> test mail' @expected.body = read_fixture('<%= action %>') @expected.date = Time.now diff --git a/railties/generators/templates/mailer_action.rhtml b/railties/generators/mailer/templates/view.rhtml index b481906829..b481906829 100644 --- a/railties/generators/templates/mailer_action.rhtml +++ b/railties/generators/mailer/templates/view.rhtml diff --git a/railties/generators/new_model.rb b/railties/generators/model/USAGE index f6fbf5f002..debf185bea 100755..100644 --- a/railties/generators/new_model.rb +++ b/railties/generators/model/USAGE @@ -1,14 +1,3 @@ -#!/usr/local/bin/ruby -require File.dirname(__FILE__) + '/../config/environment' -require 'generator' - -if ARGV.size == 1 - rails_root = File.dirname(__FILE__) + '/..' - name = ARGV.shift - Generator::Model.new(rails_root, name).generate -else - puts <<-HELP - NAME new_model - create model stub files @@ -26,6 +15,3 @@ EXAMPLE This will generate an Account class in app/models/account.rb, an AccountTest in test/unit/account_test.rb, and the directory test/fixtures/account. - -HELP -end diff --git a/railties/generators/model/model_generator.rb b/railties/generators/model/model_generator.rb new file mode 100644 index 0000000000..04540d6e40 --- /dev/null +++ b/railties/generators/model/model_generator.rb @@ -0,0 +1,10 @@ +require 'rails_generator' + +class ModelGenerator < Rails::Generator::Base + def generate + # Model class, unit test, and fixtures. + template "model.rb", "app/models/#{file_name}.rb" + template "unit_test.rb", "test/unit/#{file_name}_test.rb" + template "fixtures.yml", "test/fixtures/#{table_name}.yml" + end +end diff --git a/railties/generators/model/templates/fixtures.yml b/railties/generators/model/templates/fixtures.yml new file mode 100644 index 0000000000..a56c16462a --- /dev/null +++ b/railties/generators/model/templates/fixtures.yml @@ -0,0 +1 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html diff --git a/railties/generators/templates/model.erb b/railties/generators/model/templates/model.rb index 8d4c89e912..8d4c89e912 100644 --- a/railties/generators/templates/model.erb +++ b/railties/generators/model/templates/model.rb diff --git a/railties/generators/templates/model_test.erb b/railties/generators/model/templates/unit_test.rb index a3ad2b72fb..6162d4cf96 100644 --- a/railties/generators/templates/model_test.erb +++ b/railties/generators/model/templates/unit_test.rb @@ -4,8 +4,8 @@ require '<%= file_name %>' class <%= class_name %>Test < Test::Unit::TestCase fixtures :<%= table_name %> - # Replace this with your real tests + # Replace this with your real tests. def test_truth assert true end -end
\ No newline at end of file +end diff --git a/railties/generators/new_crud.rb b/railties/generators/new_crud.rb deleted file mode 100755 index 4eaa1cb1f3..0000000000 --- a/railties/generators/new_crud.rb +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/local/bin/ruby -require File.dirname(__FILE__) + '/../config/environment' -require 'generator' - -unless ARGV.empty? - rails_root = File.dirname(__FILE__) + '/..' - name = ARGV.shift - actions = ARGV - Generator::Model.new(rails_root, name).generate - Generator::Controller.new(rails_root, name, actions, :scaffold => true).generate -else - puts <<-END_HELP - -NAME - new_crud - create a model and a controller scaffold - -SYNOPSIS - new_crud ModelName [action ...] - -DESCRIPTION - The new_crud generator takes the name of the new model as the - first argument and an optional list of controller actions as the - subsequent arguments. All actions may be omitted since the controller - will have scaffolding automatically set up for this model. - -EXAMPLE - new_crud Account - - This will generate an Account model and controller with scaffolding. - Now create the accounts table in your database and browse to - http://localhost/account/ -- voila, you're on Rails! - -END_HELP -end diff --git a/railties/generators/scaffold/USAGE b/railties/generators/scaffold/USAGE new file mode 100644 index 0000000000..f299ee6f06 --- /dev/null +++ b/railties/generators/scaffold/USAGE @@ -0,0 +1,18 @@ +NAME + new_scaffold - create a model and a skeleton controller + +SYNOPSIS + new_scaffold ModelName [action ...] + +DESCRIPTION + The new_scaffold generator takes the name of the new model as the + first argument and an optional list of controller actions as the + subsequent arguments. Any actions with scaffolding code available + will be generated in your controller; others will be left as stubs. + +EXAMPLE + new_scaffold Account + + This will generate an Account model and controller. + Now create the accounts table in your database and browse to + http://localhost/account/ -- voila, you're on Rails! diff --git a/railties/generators/scaffold/scaffold_generator.rb b/railties/generators/scaffold/scaffold_generator.rb new file mode 100644 index 0000000000..ee556e8a03 --- /dev/null +++ b/railties/generators/scaffold/scaffold_generator.rb @@ -0,0 +1,53 @@ +require 'rails_generator' + +class ScaffoldGenerator < Rails::Generator::Base + def generate + # Model. + generator('model').generate + + # Fixtures. + template "fixtures.yml", "test/fixtures/#{table_name}.yml" + + # Controller class, functional test, helper, and views. + template "controller.rb", "app/controllers/#{file_name}_controller.rb" + template "functional_test.rb", "test/functional/#{file_name}_controller_test.rb" + template "controller/helper.rb", "app/helpers/#{file_name}_helper.rb" + + # Layout and stylesheet. + unless File.file?("app/views/layouts/scaffold.rhtml") + template "layout.rhtml", "app/views/layouts/scaffold.rhtml" + end + unless File.file?("public/stylesheets/scaffold.css") + template "style.css", "public/stylesheets/scaffold.css" + end + + # Scaffolded views. + scaffold_views.each do |action| + template "view_#{action}.rhtml", "app/views/#{file_name}/#{action}.rhtml" + end + + # Unscaffolded views. + unscaffolded_actions.each do |action| + template "controller/view.rhtml", + "app/views/#{file_name}/#{action}.rhtml", + binding + end + end + + protected + def scaffold_views + %w(list show new edit) + end + + def scaffold_actions + scaffold_views + %w(index create update destroy) + end + + def unscaffolded_actions + args - scaffold_actions + end + + def suffix + "_#{singular_name}" if options[:suffix] + end +end diff --git a/railties/generators/scaffold/templates/controller.rb b/railties/generators/scaffold/templates/controller.rb new file mode 100644 index 0000000000..c8706098da --- /dev/null +++ b/railties/generators/scaffold/templates/controller.rb @@ -0,0 +1,58 @@ +class <%= class_name %>Controller < AbstractApplicationController + model :<%= singular_name %> + layout 'scaffold' + +<% unless suffix -%> + def index + list + render_action 'list' + end +<% end -%> + +<% for action in unscaffolded_actions -%> + def <%= action %><%= suffix %> + end + +<% end -%> + def list<%= suffix %> + @<%= plural_name %> = <%= class_name %>.find_all + end + + def show<%= suffix %> + @<%= singular_name %> = <%= class_name %>.find(@params['id']) + end + + def new<%= suffix %> + @<%= singular_name %> = <%= class_name %>.new + end + + def create<%= suffix %> + @<%= singular_name %> = <%= class_name %>.new(@params['<%= singular_name %>']) + if @<%= singular_name %>.save + flash['notice'] = '<%= class_name %> was successfully created.' + redirect_to :action => 'list<%= suffix %>' + else + render_action 'new<%= suffix %>' + end + end + + def edit<%= suffix %> + @<%= singular_name %> = <%= class_name %>.find(@params['id']) + end + + def update + @<%= singular_name %> = <%= class_name %>.find(@params['<%= singular_name %>']['id']) + @<%= singular_name %>.attributes = @params['<%= singular_name %>'] + if @<%= singular_name %>.save + flash['notice'] = '<%= class_name %> was successfully updated.' + redirect_to :action => 'show<%= suffix %>', :id => @<%= singular_name %>.id + else + render_action 'edit<%= suffix %>' + end + end + + def destroy<%= suffix %> + <%= class_name %>.find(@params['id']).destroy + redirect_to :action => 'list<%= suffix %>' + end +end diff --git a/railties/generators/scaffold/templates/fixtures.yml b/railties/generators/scaffold/templates/fixtures.yml new file mode 100644 index 0000000000..ea67f36ad8 --- /dev/null +++ b/railties/generators/scaffold/templates/fixtures.yml @@ -0,0 +1,7 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html + +first_<%= singular_name %>: + id: 1 + +another_<%= singular_name %>: + id: 2 diff --git a/railties/generators/scaffold/templates/functional_test.rb b/railties/generators/scaffold/templates/functional_test.rb new file mode 100644 index 0000000000..e82349375b --- /dev/null +++ b/railties/generators/scaffold/templates/functional_test.rb @@ -0,0 +1,109 @@ +require File.dirname(__FILE__) + '/../test_helper' +require '<%= file_name %>_controller' + +# Re-raise errors caught by the controller. +class <%= class_name %>Controller; def rescue_action(e) raise e end; end + +class <%= class_name %>ControllerTest < Test::Unit::TestCase + fixtures :<%= table_name %> + + def setup + @controller = <%= class_name %>Controller.new + @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new + end + +<% for action in unscaffolded_actions -%> + def test_<%= action %> + process :<%= action %> + assert_success + assert_rendered_file '<%= action %>' + end + +<% end -%> +<% unless suffix -%> + def test_index + process :index + assert_success + assert_rendered_file 'list' + end + +<% end -%> + def test_list<%= suffix %> + process :list<%= suffix %> + assert_success + assert_rendered_file 'list<%= suffix %>' + assert_template_has '<%= plural_name %>' + end + + def test_show<%= suffix %> + process :show<%= suffix %>, 'id' => 1 + assert_success + assert_rendered_file 'show' + assert_template_has '<%= singular_name %>' + assert_valid_record '<%= singular_name %>' + end + + def test_show_missing_<%= suffix || 'record' %> + process :show<%= suffix %> + assert_success + assert_rendered_file 'error' + end + + def test_new<%= suffix %> + process :new<%= suffix %> + assert_success + assert_rendered_file 'new<%= suffix %>' + assert_template_has '<%= singular_name %>' + end + + def test_create + num_<%= plural_name %> = <%= class_name %>.find_all.size + + process :create<%= suffix %>, '<%= singular_name %>' => { } + assert_redirected_to :action => 'list<%= suffix %>' + + assert_equal num_<%= plural_name %> + 1, <%= class_name %>.find_all.size + end + + def test_edit<%= suffix %> + process :edit<%= suffix %>, 'id' => 1 + assert_success + assert_rendered_file 'edit<%= suffix %>' + assert_template_has '<%= singular_name %>' + assert_valid_record '<%= singular_name %>' + end + + def test_edit_missing_<%= suffix || 'record' %> + process :edit<%= suffix %> + assert_success + assert_rendered_file 'error' + end + + def test_update<%= suffix %> + process :update<%= suffix %>, 'id' => 1 + assert_redirected_to :action => 'show<%= suffix %>', :id => 1 + end + + def test_update_missing_<%= suffix || 'record' %> + process :update<%= suffix %>, '<%= singular_name %>' => {} + assert_success + assert_rendered_file 'error' + end + + def test_destroy<%= suffix %> + assert_not_nil <%= class_name %>.find(1) + + process :destroy, 'id' => 1 + assert_redirected_to :action => 'list<%= suffix %>' + + assert_raise(ActiveRecord::RecordNotFound) { + <%= singular_name %> = <%= class_name %>.find(1) + } + end + + def test_destroy_missing_<%= suffix || 'record' %> + process :destroy<%= suffix %> + assert_success + assert_rendered_file 'error' + end +end diff --git a/railties/generators/scaffold/templates/layout.rhtml b/railties/generators/scaffold/templates/layout.rhtml new file mode 100644 index 0000000000..59d5585b51 --- /dev/null +++ b/railties/generators/scaffold/templates/layout.rhtml @@ -0,0 +1,11 @@ +<html> +<head> + <title>Scaffolding: <%%= controller.controller_name %>#<%%= controller.action_name %></title> + <link href="/stylesheets/scaffold.css" rel="stylesheet" type="text/css" /> +</head> +<body> + +<%%= @content_for_layout %> + +</body> +</html> diff --git a/railties/generators/scaffold/templates/style.css b/railties/generators/scaffold/templates/style.css new file mode 100644 index 0000000000..2db43b7fa1 --- /dev/null +++ b/railties/generators/scaffold/templates/style.css @@ -0,0 +1,17 @@ +body { background-color: #fff; color: #333; } + +body, p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; +} + +pre { + background-color: #eee; + padding: 10px; + font-size: 11px; +} + +a { color: #000; } +a:visited { color: #666; } +a:hover { color: #fff; background-color:#000; } diff --git a/railties/generators/scaffold/templates/view_edit.rhtml b/railties/generators/scaffold/templates/view_edit.rhtml new file mode 100644 index 0000000000..fec09e2cbe --- /dev/null +++ b/railties/generators/scaffold/templates/view_edit.rhtml @@ -0,0 +1,6 @@ +<h1>Editing <%= singular_name %></h1> + +<%%= form '<%= singular_name %>', :action => 'update<%= suffix %>' %> + +<%%= link_to 'Show', :action => 'show<%= suffix %>', :id => @<%= singular_name %>.id %> | +<%%= link_to 'Back', :action => 'list<%= suffix %>' %> diff --git a/railties/generators/scaffold/templates/view_list.rhtml b/railties/generators/scaffold/templates/view_list.rhtml new file mode 100644 index 0000000000..068fd67472 --- /dev/null +++ b/railties/generators/scaffold/templates/view_list.rhtml @@ -0,0 +1,24 @@ +<h1>Listing <%= plural_name %></h1> + +<table> + <tr> +<%% for column in <%= class_name %>.content_columns %> + <th><%%= column.human_name %></th> +<%% end %> + </tr> + +<%% for <%= singular_name %> in @<%= plural_name %> %> + <tr> + <%% for column in <%= class_name %>.content_columns %> + <td><%%=h <%= singular_name %>[column.name] %></td> + <%% end %> + <td><%%= link_to 'Show', :action => 'show<%= suffix %>', :id => <%= singular_name %>.id %></td> + <td><%%= link_to 'Edit', :action => 'edit<%= suffix %>', :id => <%= singular_name %>.id %></td> + <td><%%= link_to 'Destroy', :action => 'destroy<%= suffix %>', :id => <%= singular_name %>.id %></td> + </tr> +<%% end %> +</table> + +<br /> + +<%%= link_to 'New <%= singular_name %>', :action => 'new<%= suffix %>' %> diff --git a/railties/generators/scaffold/templates/view_new.rhtml b/railties/generators/scaffold/templates/view_new.rhtml new file mode 100644 index 0000000000..840bb02775 --- /dev/null +++ b/railties/generators/scaffold/templates/view_new.rhtml @@ -0,0 +1,5 @@ +<h1>New <%= @singular_name %></h1> + +<%%= form '<%= singular_name %>', :action => 'create<%= suffix %>' %> + +<%%= link_to 'Back', :action => 'list<%= suffix %>' %> diff --git a/railties/generators/scaffold/templates/view_show.rhtml b/railties/generators/scaffold/templates/view_show.rhtml new file mode 100644 index 0000000000..30a3242f14 --- /dev/null +++ b/railties/generators/scaffold/templates/view_show.rhtml @@ -0,0 +1,8 @@ +<%% for column in <%= class_name %>.content_columns %> +<p> + <b><%%= column.human_name %>:</b> <%%= @<%= singular_name %>[column.name] %> +</p> +<%% end %> + +<%%= link_to 'Edit', :action => 'edit<%= suffix %>', :id => @<%= singular_name %>.id %> | +<%%= link_to 'Back', :action => 'list<%= suffix %>' %> diff --git a/railties/generators/templates/controller.erb b/railties/generators/templates/controller.erb deleted file mode 100644 index 600f5d2c59..0000000000 --- a/railties/generators/templates/controller.erb +++ /dev/null @@ -1,19 +0,0 @@ -class <%= class_name %>Controller < AbstractApplicationController - helper :<%= file_name %> -<% if options[:scaffold] -%> - model :<%= file_name %> - scaffold :<%= options[:scaffold] %> - - <%- for action in actions -%> - #def <%= action %> - #end - - <%- end -%> -<% else -%> - <%- for action in actions -%> - def <%= action %> - end - - <%- end -%> -<% end -%> -end diff --git a/railties/generators/templates/controller_view.rhtml b/railties/generators/templates/controller_view.rhtml deleted file mode 100644 index d8a310df50..0000000000 --- a/railties/generators/templates/controller_view.rhtml +++ /dev/null @@ -1,10 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> -<head> - <title><%= class_name %>#<%= action %></title> -</head> -<body> -<h1><%= class_name %>#<%= action %></h1> -<p>Find me in app/views/<%= file_name %>/<%= action %>.rhtml</p> -</body> -</html> diff --git a/railties/lib/rails_generator.rb b/railties/lib/rails_generator.rb new file mode 100644 index 0000000000..9e64f5f4c5 --- /dev/null +++ b/railties/lib/rails_generator.rb @@ -0,0 +1,175 @@ +require 'fileutils' + +module Rails + module Generator + class GeneratorError < StandardError; end + class UsageError < GeneratorError; end + + CONTRIB_ROOT = "#{RAILS_ROOT}/script/generators" + BUILTIN_ROOT = "#{File.dirname(__FILE__)}/../generators" + DEFAULT_SEARCH_PATHS = [CONTRIB_ROOT, BUILTIN_ROOT] + + class << self + def instance(name, args = [], search_paths = DEFAULT_SEARCH_PATHS) + # RAILS_ROOT constant must be set. + unless Object.const_get(:RAILS_ROOT) + raise GeneratorError, "RAILS_ROOT must be set. Did you require 'config/environment'?" + end + + # Force canonical name. + name = Inflector.underscore(name.downcase) + + # Search for filesystem path to requested generator. + unless path = find_generator_path(name, search_paths) + raise GeneratorError, "#{name} generator not found." + end + + # Check for templates directory. + template_root = "#{path}/templates" + unless File.directory?(template_root) + raise GeneratorError, "missing template directory #{template_root}" + end + + # Require class file according to naming convention. + require "#{path}/#{name}_generator.rb" + + # Find class according to naming convention. Allow Nesting::In::Modules. + class_name = Inflector.classify("#{name}_generator") + unless klass = find_generator_class(name) + raise GeneratorError, "no #{class_name} class defined in #{path}/#{name}_generator.rb" + end + + # Instantiate and return generator. + klass.new(template_root, RAILS_ROOT, search_paths, args) + end + + + def builtin_generators + generators([BUILTIN_ROOT]) + end + + def contrib_generators + generators([CONTRIB_ROOT]) + end + + def generators(search_paths) + generator_paths(search_paths).keys.uniq.sort + end + + # Find all generator paths. + def generator_paths(search_paths) + @paths ||= {} + unless @paths[search_paths] + paths = Hash.new { |h,k| h[k] = [] } + search_paths.each do |path| + Dir["#{path}/[a-z]*"].each do |dir| + paths[File.basename(dir)] << dir if File.directory?(dir) + end + end + @paths[search_paths] = paths + end + @paths[search_paths] + end + + def find_generator_path(name, search_paths) + generator_paths(search_paths)[name].first + end + + # Find all generator classes. + def generator_classes + classes = Hash.new { |h,k| h[k] = [] } + class_re = /([^:]+)Generator$/ + ObjectSpace.each_object(Class) do |object| + if md = class_re.match(object.name) and object < Rails::Generator::Base + classes[Inflector.underscore(md.captures.first)] << object + end + end + classes + end + + def find_generator_class(name) + generator_classes[name].first + end + end + + + # Talk about generators. + class Base + attr_reader :template_root, :destination_root, :args, :options, + :class_name, :singular_name, :plural_name + + alias_method :file_name, :singular_name + alias_method :table_name, :plural_name + + def self.generator_name + Inflector.underscore(name.gsub('Generator', '')) + end + + def initialize(template_root, destination_root, search_paths, args) + @template_root, @destination_root = template_root, destination_root + usage if args.empty? + @search_paths, @original_args = search_paths, args.dup + @class_name, @singular_name, @plural_name = inflect_names(args.shift) + @options = extract_options!(args) + @args = args + end + + protected + # Look up another generator with the same arguments. + def generator(name) + Rails::Generator.instance(name, @original_args, @search_paths) + end + + # Generate a file for a Rails application using an ERuby template. + # Looks up and evalutes a template by name and writes the result + # to a file relative to +destination_root+. The template + # is evaluated in the context of the optional eval_binding argument. + # + # The ERB template uses explicit trim mode to best control the + # proliferation of whitespace in generated code. <%- trims leading + # whitespace; -%> trims trailing whitespace including one newline. + def template(template_name, destination_path, eval_binding = nil) + # Determine full paths for source and destination files. + template_path = find_template_path(template_name) + destination_path = File.join(destination_root, destination_path) + + # Create destination directories. + FileUtils.mkdir_p(File.dirname(destination_path)) + + # Render template and write result. + eval_binding ||= binding + contents = ERB.new(File.read(template_path), nil, '-').result(eval_binding) + File.open(destination_path, 'w') { |file| file.write(contents) } + end + + def usage + raise UsageError.new, File.read(usage_path) + end + + private + def find_template_path(template_name) + name, path = template_name.split('/', 2) + if path.nil? + File.join(template_root, name) + elsif generator_path = Rails::Generator.find_generator_path(name, @search_paths) + File.join(generator_path, 'templates', path) + end + end + + def inflect_names(name) + camel = Inflector.camelize(Inflector.underscore(name)) + under = Inflector.underscore(camel) + plural = Inflector.pluralize(under) + [camel, under, plural] + end + + def extract_options!(args) + if args.last.is_a?(Hash) then args.pop else {} end + end + + def usage_path + "#{template_root}/../USAGE" + end + end + end +end diff --git a/railties/test/generators/missing_class/missing_class_generator.rb b/railties/test/generators/missing_class/missing_class_generator.rb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/railties/test/generators/missing_class/missing_class_generator.rb diff --git a/railties/test/rails_generator_test.rb b/railties/test/rails_generator_test.rb new file mode 100644 index 0000000000..b63840d338 --- /dev/null +++ b/railties/test/rails_generator_test.rb @@ -0,0 +1,95 @@ +$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib" +RAILS_ROOT = File.dirname(__FILE__) + +require File.dirname(__FILE__) + '/../../activerecord/lib/active_record/support/inflector' +require 'rails_generator' +require 'test/unit' + + +class RailsGeneratorTest < Test::Unit::TestCase + BUILTINS = %w(controller mailer model scaffold) + + def test_instance_builtins + BUILTINS.each do |name| + object = nil + assert_nothing_raised { object = Rails::Generator.instance(name, ['foo']) } + assert_not_nil object + assert_match /#{name.capitalize}Generator/, object.class.name + assert_respond_to object, :generate + end + end + + def test_instance_without_rails_root + old_verbose, $VERBOSE = $VERBOSE, nil + old_rails_root = Object.const_get(:RAILS_ROOT) + begin + Object.const_set(:RAILS_ROOT, nil) + assert_raise(Rails::Generator::GeneratorError) { + Rails::Generator.instance('model', ['name']) + } + ensure + Object.const_set(:RAILS_ROOT, old_rails_root) + $VERBOSE = old_verbose + end + end + + def test_instance_not_found + assert_raise(Rails::Generator::GeneratorError) { + Rails::Generator.instance('foobar') + } + end + + def test_instance_missing_templates + assert_raise(Rails::Generator::GeneratorError) { + Rails::Generator.instance('missing_templates') + } + end + + def test_instance_missing_generator + assert_raise(LoadError) { + Rails::Generator.instance('missing_generator') + } + end + + def test_instance_missing_class + assert_raise(Rails::Generator::GeneratorError) { + Rails::Generator.instance('missing_class') + } + end + + def test_builtin_generators + assert_nothing_raised { + assert_equal [], Rails::Generator.builtin_generators - BUILTINS + } + end + + def test_generator_name + assert_equal 'model', Rails::Generator.instance('model', ['name']).class.generator_name + end + + def test_generator_usage + assert_raise(Rails::Generator::UsageError) { + assert_equal 'model', Rails::Generator.instance('model') + } + end + + def test_generator_vars + model = Rails::Generator.instance('model', ['model']) + assert_equal "#{Rails::Generator::BUILTIN_ROOT}/model/templates", model.template_root + assert_equal RAILS_ROOT, model.destination_root + assert_equal 'Model', model.class_name + assert_equal 'model', model.singular_name + assert_equal 'models', model.plural_name + assert_equal model.singular_name, model.file_name + assert_equal model.plural_name, model.table_name + assert_equal [], model.args + end + + def test_generator_generator + assert_nothing_raised { + model = Rails::Generator.instance('model', ['name']) + mailer = model.send(:generator, 'mailer') + assert_equal 'mailer', mailer.class.generator_name + } + end +end |