From 8c5622f0d4d3b5c6bce52b0eb22756a83060b1b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 27 Jun 2009 13:03:07 +0200 Subject: Initial scratch of model generators. --- .../active_record/model/model_generator.rb | 33 ++++++++++ .../active_record/model/templates/migration.rb | 16 +++++ .../active_record/model/templates/model.rb | 5 ++ railties/lib/generators/base.rb | 5 +- railties/lib/generators/generated_attribute.rb | 3 +- railties/lib/generators/named_base.rb | 4 +- .../rails/app/templates/config/environment.rb | 6 +- railties/lib/generators/rails/model/USAGE | 27 ++++++++ .../lib/generators/rails/model/model_generator.rb | 8 +++ .../generators/test_unit/model/model_generator.rb | 24 ++++++++ .../test_unit/model/templates/fixtures.yml | 19 ++++++ .../test_unit/model/templates/unit_test.rb | 8 +++ .../test/generators/controller_generator_test.rb | 2 + railties/test/generators/generators_test_helper.rb | 14 +++-- railties/test/generators/helper_generator_test.rb | 19 ++++++ railties/test/generators/mailer_generator_test.rb | 6 +- railties/test/generators/model_generator_test.rb | 72 ++++++++++++++++++++++ .../test/generators/observer_generator_test.rb | 6 +- 18 files changed, 256 insertions(+), 21 deletions(-) create mode 100644 railties/lib/generators/active_record/model/model_generator.rb create mode 100644 railties/lib/generators/active_record/model/templates/migration.rb create mode 100644 railties/lib/generators/active_record/model/templates/model.rb create mode 100644 railties/lib/generators/rails/model/USAGE create mode 100644 railties/lib/generators/rails/model/model_generator.rb create mode 100644 railties/lib/generators/test_unit/model/model_generator.rb create mode 100644 railties/lib/generators/test_unit/model/templates/fixtures.yml create mode 100644 railties/lib/generators/test_unit/model/templates/unit_test.rb create mode 100644 railties/test/generators/model_generator_test.rb diff --git a/railties/lib/generators/active_record/model/model_generator.rb b/railties/lib/generators/active_record/model/model_generator.rb new file mode 100644 index 0000000000..448bf5c37f --- /dev/null +++ b/railties/lib/generators/active_record/model/model_generator.rb @@ -0,0 +1,33 @@ +module ActiveRecord + module Generators + class ModelGenerator < Base + argument :attributes, :type => :hash, :default => {}, :banner => "field:type, field:type" + + check_class_collision + + # TODO Add parent support + + # TODO Add DEFAULTS support + class_option :skip_timestamps, :type => :boolean, :default => false, + :desc => "Don't add timestamps to the migration file" + + # TODO Make this a invoke_if + # TODO Add DEFAULTS support + class_option :skip_migration, :type => :boolean, :default => false, + :desc => "Don't generate a migration file" + + def create_model_file + template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb") + end + + # TODO Add migration support + def create_migration_file +# unless options[:skip_migration] +# m.migration_template 'migration.rb', 'db/migrate', :assigns => { +# :migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}" +# }, :migration_file_name => "create_#{file_path.gsub(/\//, '_').pluralize}" +# end + end + end + end +end diff --git a/railties/lib/generators/active_record/model/templates/migration.rb b/railties/lib/generators/active_record/model/templates/migration.rb new file mode 100644 index 0000000000..382fd1156e --- /dev/null +++ b/railties/lib/generators/active_record/model/templates/migration.rb @@ -0,0 +1,16 @@ +class <%= migration_name %> < ActiveRecord::Migration + def self.up + create_table :<%= table_name %> do |t| +<% for attribute in attributes -%> + t.<%= attribute.type %> :<%= attribute.name %> +<% end -%> +<% unless options[:skip_timestamps] %> + t.timestamps +<% end -%> + end + end + + def self.down + drop_table :<%= table_name %> + end +end diff --git a/railties/lib/generators/active_record/model/templates/model.rb b/railties/lib/generators/active_record/model/templates/model.rb new file mode 100644 index 0000000000..0656b06dfe --- /dev/null +++ b/railties/lib/generators/active_record/model/templates/model.rb @@ -0,0 +1,5 @@ +class <%= class_name %> < ActiveRecord::Base +<% attributes.select {|attr| attr.reference? }.each do |attribute| -%> + belongs_to :<%= attribute.name %> +<% end -%> +end diff --git a/railties/lib/generators/base.rb b/railties/lib/generators/base.rb index a29bda6afd..e5e2c03b48 100644 --- a/railties/lib/generators/base.rb +++ b/railties/lib/generators/base.rb @@ -96,7 +96,7 @@ module Rails class_option name, options.merge!(:type => :default, :default => DEFAULTS[name]) class_eval <<-METHOD, __FILE__, __LINE__ - def invoke_#{name} + def invoke_for_#{name} return unless options[#{name.inspect}] klass = Rails::Generators.find_by_namespace(options[#{name.inspect}], @@ -143,10 +143,11 @@ module Rails options = default_options.dup options[:desc] ||= "Indicates when to use #{name.to_s.humanize}" + # TODO Reverse --name to --skip-name if default is given. class_option name, options.merge!(:type => :boolean, :default => DEFAULTS[name] || false) class_eval <<-METHOD, __FILE__, __LINE__ - def invoke_#{name} + def invoke_if_#{name} return unless options[#{name.inspect}] klass = Rails::Generators.find_by_namespace(#{name.inspect}, diff --git a/railties/lib/generators/generated_attribute.rb b/railties/lib/generators/generated_attribute.rb index 0ead945281..7cd1fed8a4 100644 --- a/railties/lib/generators/generated_attribute.rb +++ b/railties/lib/generators/generated_attribute.rb @@ -1,11 +1,10 @@ module Rails module Generators class GeneratedAttribute - attr_accessor :name, :type, :column + attr_accessor :name, :type def initialize(name, type) @name, @type = name, type.to_sym - @column = ActiveRecord::ConnectionAdapters::Column.new(@name, nil, @type) end def field_type diff --git a/railties/lib/generators/named_base.rb b/railties/lib/generators/named_base.rb index 6ff5bb7644..4a5aba38d8 100644 --- a/railties/lib/generators/named_base.rb +++ b/railties/lib/generators/named_base.rb @@ -69,8 +69,8 @@ module Rails # Convert attributes hash into an array with GeneratedAttribute objects. # def parse_attributes! #:nodoc: - attributes.map! do |name, type| - Rails::Generator::GeneratedAttribute.new(name, type) + self.attributes = (attributes || {}).map do |name, type| + Rails::Generators::GeneratedAttribute.new(name, type) end end diff --git a/railties/lib/generators/rails/app/templates/config/environment.rb b/railties/lib/generators/rails/app/templates/config/environment.rb index d1080cb529..84c5abf1e9 100644 --- a/railties/lib/generators/rails/app/templates/config/environment.rb +++ b/railties/lib/generators/rails/app/templates/config/environment.rb @@ -26,14 +26,14 @@ Rails::Initializer.run do |config| # Skip frameworks you're not going to use. To use Rails without a database, # you must remove the Active Record framework. -<%- if options[:skip_activerecord] -%> +<% if options[:skip_activerecord] -%> config.frameworks -= [ :active_record ] -<%- else -%> +<% else -%> # config.frameworks -= [ :active_record, :active_resource, :action_mailer ] # Activate observers that should always be running # config.active_record.observers = :cacher, :garbage_collector, :forum_observer -<%- end -%> +<% end -%> # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. diff --git a/railties/lib/generators/rails/model/USAGE b/railties/lib/generators/rails/model/USAGE new file mode 100644 index 0000000000..73c8b69153 --- /dev/null +++ b/railties/lib/generators/rails/model/USAGE @@ -0,0 +1,27 @@ +Description: + Stubs out a new model. Pass the model name, either CamelCased or + under_scored, and an optional list of attribute pairs as arguments. + + Attribute pairs are field:type arguments specifying the + model's attributes. Timestamps are added by default, so you don't have to + specify them by hand as 'created_at:datetime updated_at:datetime'. + + You don't have to think up every attribute up front, but it helps to + sketch out a few so you can start working with the model immediately. + + This generator invokes your configured ORM and test framework, which + defaults to ActiveRecord and TestUnit. + +Examples: + `./script/generate model account` + + For ActiveRecord and TestUnit it creates: + + Model: app/models/account.rb + Test: test/unit/account_test.rb + Fixtures: test/fixtures/accounts.yml + Migration: db/migrate/XXX_add_accounts.rb + + `./script/generate model post title:string body:text published:boolean` + + Creates a Post model with a string title, text body, and published flag. diff --git a/railties/lib/generators/rails/model/model_generator.rb b/railties/lib/generators/rails/model/model_generator.rb new file mode 100644 index 0000000000..7a732bfd0d --- /dev/null +++ b/railties/lib/generators/rails/model/model_generator.rb @@ -0,0 +1,8 @@ +module Rails + module Generators + class ModelGenerator < NamedBase + argument :attributes, :type => :hash, :default => {}, :banner => "field:type, field:type" + invoke_for :orm, :test_framework + end + end +end diff --git a/railties/lib/generators/test_unit/model/model_generator.rb b/railties/lib/generators/test_unit/model/model_generator.rb new file mode 100644 index 0000000000..901ed70468 --- /dev/null +++ b/railties/lib/generators/test_unit/model/model_generator.rb @@ -0,0 +1,24 @@ +module TestUnit + module Generators + class ModelGenerator < Base + argument :attributes, :type => :hash, :default => {}, :banner => "field:type, field:type" + + check_class_collision :suffix => "Test" + + # TODO Add DEFAULTS support + class_option :skip_fixture, :type => :boolean, :default => false, + :desc => "Don't generate a fixture file" + + def create_test_file + template 'unit_test.rb', File.join('test/unit', class_path, "#{file_name}_test.rb") + end + + # TODO Add fixture replacement support + def create_fixture_file + unless options[:skip_fixture] + template 'fixtures.yml', File.join('test/fixtures', "#{table_name}.yml") + end + end + end + end +end diff --git a/railties/lib/generators/test_unit/model/templates/fixtures.yml b/railties/lib/generators/test_unit/model/templates/fixtures.yml new file mode 100644 index 0000000000..c21035113e --- /dev/null +++ b/railties/lib/generators/test_unit/model/templates/fixtures.yml @@ -0,0 +1,19 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html + +<% unless attributes.empty? -%> +one: +<% for attribute in attributes -%> + <%= attribute.name %>: <%= attribute.default %> +<% end -%> + +two: +<% for attribute in attributes -%> + <%= attribute.name %>: <%= attribute.default %> +<% end -%> +<% else -%> +# one: +# column: value +# +# two: +# column: value +<% end -%> diff --git a/railties/lib/generators/test_unit/model/templates/unit_test.rb b/railties/lib/generators/test_unit/model/templates/unit_test.rb new file mode 100644 index 0000000000..3e0bc29d3a --- /dev/null +++ b/railties/lib/generators/test_unit/model/templates/unit_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class <%= class_name %>Test < ActiveSupport::TestCase + # Replace this with your real tests. + test "the truth" do + assert true + end +end diff --git a/railties/test/generators/controller_generator_test.rb b/railties/test/generators/controller_generator_test.rb index e10580f40f..d0c963a9ac 100644 --- a/railties/test/generators/controller_generator_test.rb +++ b/railties/test/generators/controller_generator_test.rb @@ -20,6 +20,8 @@ class ControllerGeneratorTest < GeneratorsTestCase assert_match /The name 'ObjectController' is either already used in your application or reserved/, content end + # No need to spec content since it's already spec'ed on helper generator. + # def test_invokes_helper run_generator assert_file "app/helpers/account_helper.rb" diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb index 1fcfdaebb9..29ff306551 100644 --- a/railties/test/generators/generators_test_helper.rb +++ b/railties/test/generators/generators_test_helper.rb @@ -34,15 +34,17 @@ class GeneratorsTestCase < Test::Unit::TestCase end alias :silence :capture - def assert_file(relative, content=nil) + def assert_file(relative, *contents) absolute = File.join(destination_root, relative) assert File.exists?(absolute) - case content - when String - assert_equal content, File.read(absolute) - when Regexp - assert_match content, File.read(absolute) + contents.each do |content| + case content + when String + assert_equal content, File.read(absolute) + when Regexp + assert_match content, File.read(absolute) + end end end diff --git a/railties/test/generators/helper_generator_test.rb b/railties/test/generators/helper_generator_test.rb index 9b2f277f54..b2533e5315 100644 --- a/railties/test/generators/helper_generator_test.rb +++ b/railties/test/generators/helper_generator_test.rb @@ -33,6 +33,25 @@ class HelperGeneratorTest < GeneratorsTestCase assert_match /The name 'AnotherObjectHelperTest' is either already used in your application or reserved/, content end + def test_namespaced_and_not_namespaced_helpers + run_generator ["products"] + + # We have to require the generated helper to show the problem because + # the test helpers just check for generated files and contents but + # do not actually load them. But they have to be loaded (as in a real environment) + # to make the second generator run fail + require "#{destination_root}/app/helpers/products_helper" + + assert_nothing_raised do + begin + run_generator ["admin::products"] + ensure + # cleanup + Object.send(:remove_const, :ProductsHelper) + end + end + end + protected def run_generator(args=["admin"]) diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb index 710847587d..3e5dc60361 100644 --- a/railties/test/generators/mailer_generator_test.rb +++ b/railties/test/generators/mailer_generator_test.rb @@ -18,9 +18,9 @@ class MailerGeneratorTest < GeneratorsTestCase def test_invokes_default_test_framework run_generator - assert_file "test/unit/notifier_test.rb" - assert_file "test/fixtures/notifier/foo" - assert_file "test/fixtures/notifier/bar" + assert_file "test/unit/notifier_test.rb", /class NotifierTest < ActionMailer::TestCase/ + assert_file "test/fixtures/notifier/foo", /app\/views\/notifier\/foo/ + assert_file "test/fixtures/notifier/bar", /app\/views\/notifier\/bar/ end def test_invokes_default_template_engine diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb new file mode 100644 index 0000000000..425c4f581a --- /dev/null +++ b/railties/test/generators/model_generator_test.rb @@ -0,0 +1,72 @@ +require 'abstract_unit' +require 'generators/generators_test_helper' +require 'generators/active_record/model/model_generator' +require 'generators/rails/model/model_generator' +require 'generators/test_unit/model/model_generator' + +class ModelGeneratorTest < GeneratorsTestCase + + def test_invokes_default_orm + run_generator + assert_file "app/models/account.rb", /class Account < ActiveRecord::Base/ + end + + def test_invokes_default_test_framework + run_generator + assert_file "test/unit/account_test.rb", /class AccountTest < ActiveSupport::TestCase/ + assert_file "test/fixtures/accounts.yml", /name: MyString/, /age: 1/ + end + + def test_fixtures_are_skipped + run_generator ["account", "--skip-fixture"] + assert_no_file "test/fixtures/accounts.yml" + end + + def test_check_class_collision + content = capture(:stderr){ run_generator ["object"] } + assert_match /The name 'Object' is either already used in your application or reserved/, content + end + +# def test_model_skip_migration_skips_migration +# run_generator('model', %w(Product name:string --skip-migration)) + +# assert_generated_model_for :product +# assert_generated_fixtures_for :products +# assert_skipped_migration :create_products +# end + +# def test_model_with_attributes_generates_resources_with_attributes +# run_generator('model', %w(Product name:string supplier_id:integer created_at:timestamp)) + +# assert_generated_model_for :product +# assert_generated_fixtures_for :products +# assert_generated_migration :create_products do |t| +# assert_generated_column t, :name, :string +# assert_generated_column t, :supplier_id, :integer +# assert_generated_column t, :created_at, :timestamp +# end +# end + +# def test_model_with_reference_attributes_generates_belongs_to_associations +# run_generator('model', %w(Product name:string supplier:references)) + +# assert_generated_model_for :product do |body| +# assert body =~ /^\s+belongs_to :supplier/, "#{body.inspect} should contain 'belongs_to :supplier'" +# end +# end + +# def test_model_with_belongs_to_attributes_generates_belongs_to_associations +# run_generator('model', %w(Product name:string supplier:belongs_to)) + +# assert_generated_model_for :product do |body| +# assert body =~ /^\s+belongs_to :supplier/, "#{body.inspect} should contain 'belongs_to :supplier'" +# end +# end + + protected + + def run_generator(args=["Account", "name:string", "age:integer"]) + silence(:stdout) { Rails::Generators::ModelGenerator.start args, :root => destination_root } + end + +end diff --git a/railties/test/generators/observer_generator_test.rb b/railties/test/generators/observer_generator_test.rb index 780c854d85..e2b027028a 100644 --- a/railties/test/generators/observer_generator_test.rb +++ b/railties/test/generators/observer_generator_test.rb @@ -6,19 +6,19 @@ require 'generators/test_unit/observer/observer_generator' class ObserverGeneratorTest < GeneratorsTestCase - def test_observer_skeleton_is_created + def test_invokes_default_orm run_generator assert_file "app/models/account_observer.rb", /class AccountObserver < ActiveRecord::Observer/ end - def test_observer_with_class_path_skeleton_is_created + def test_invokes_default_orm_with_class_path run_generator ["admin/account"] assert_file "app/models/admin/account_observer.rb", /class Admin::AccountObserver < ActiveRecord::Observer/ end def test_invokes_default_test_framework run_generator - assert_file "test/unit/account_observer_test.rb" + assert_file "test/unit/account_observer_test.rb", /class AccountObserverTest < ActiveSupport::TestCase/ end def test_logs_if_the_test_framework_cannot_be_found -- cgit v1.2.3