/generators/dry_scaffold/dry_scaffold_generator.rb
Ruby | 415 lines | 319 code | 70 blank | 26 comment | 14 complexity | 76dc161e061478f82e88d684d26800bf MD5 | raw file
- require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib', 'dry_generator'))
- require File.expand_path(File.join(File.dirname(__FILE__), '..', 'dry_model', 'dry_model_generator'))
- class DryScaffoldGenerator < DryGenerator
-
- # Banner: Generator arguments and options.
- BANNER_ARGS = [
- "[_actions:new,create,...]",
- "[_formats:html,json,...]",
- DryModelGenerator::BANNER_ARGS
- ].freeze
- BANNER_OPTIONS = [
- "[--skip-pagination]",
- "[--skip-search]",
- "[--skip-resourceful]",
- "[--skip-formtastic]",
- "[--skip-views]",
- "[--skip-builders]",
- "[--skip-helpers]",
- "[--layout]",
- DryModelGenerator::BANNER_OPTIONS
- ].freeze
-
- # Paths.
- CONTROLLERS_PATH = File.join('app', 'controllers').freeze
- HELPERS_PATH = File.join('app', 'helpers').freeze
- VIEWS_PATH = File.join('app', 'views').freeze
- LAYOUTS_PATH = File.join(VIEWS_PATH, 'layouts').freeze
-
- ROUTES_FILE_PATH = File.join(RAILS_ROOT, 'config', 'routes.rb').freeze
-
- # Formats.
- DEFAULT_RESPOND_TO_FORMATS = [:html, :xml, :json].freeze
- ENHANCED_RESPOND_TO_FORMATS = [:yml, :yaml, :txt, :text, :atom, :rss].freeze
- RESPOND_TO_FEED_FORMATS = [:atom, :rss].freeze
-
- # Actions.
- DEFAULT_MEMBER_ACTIONS = [:show, :new, :edit, :create, :update, :destroy].freeze
- DEFAULT_MEMBER_AUTOLOAD_ACTIONS = (DEFAULT_MEMBER_ACTIONS - [:new, :create])
- DEFAULT_COLLECTION_ACTIONS = [:index].freeze
- DEFAULT_COLLECTION_AUTOLOAD_ACTIONS = DEFAULT_COLLECTION_ACTIONS
- DEFAULT_CONTROLLER_ACTIONS = (DEFAULT_COLLECTION_ACTIONS + DEFAULT_MEMBER_ACTIONS)
-
- DEFAULT_VIEW_TEMPLATE_FORMAT = :haml
-
- RESOURCEFUL_COLLECTION_NAME = 'collection'.freeze
- RESOURCEFUL_SINGULAR_NAME = 'resource'.freeze
-
- # :{action} => [:{partial}, ...]
- ACTION_VIEW_TEMPLATES = {
- :index => [:item],
- :show => [],
- :new => [:form],
- :edit => [:form]
- }.freeze
-
- ACTION_FORMAT_BUILDERS = {
- :index => [:atom, :rss]
- }
-
- attr_reader :controller_name,
- :controller_class_path,
- :controller_file_path,
- :controller_class_nesting,
- :controller_class_nesting_depth,
- :controller_class_name,
- :controller_underscore_name,
- :controller_singular_name,
- :controller_plural_name,
- :collection_name,
- :model_singular_name,
- :model_plural_name,
- :view_template_format,
- :actions,
- :formats,
- :config
-
- alias_method :controller_file_name, :controller_underscore_name
- alias_method :controller_table_name, :controller_plural_name
-
- def initialize(runtime_args, runtime_options = {})
- super(runtime_args, runtime_options)
-
- @controller_name = @name.pluralize
- base_name, @controller_class_path, @controller_file_path, @controller_class_nesting, @controller_class_nesting_depth = extract_modules(@controller_name)
- @controller_class_name_without_nesting, @controller_underscore_name, @controller_plural_name = inflect_names(base_name)
- @controller_singular_name = base_name.singularize
-
- if @controller_class_nesting.empty?
- @controller_class_name = @controller_class_name_without_nesting
- else
- @controller_class_name = "#{@controller_class_nesting}::#{@controller_class_name_without_nesting}"
- end
-
- @view_template_format = options[:view_template_format] || DEFAULT_VIEW_TEMPLATE_FORMAT
-
- @attributes ||= []
- @args_for_model ||= []
-
- # Non-attribute args, i.e. "_actions:new,create".
- @args.each do |arg|
- arg_entities = arg.split(':')
- if arg =~ /^#{NON_ATTR_ARG_KEY_PREFIX}/
- if arg =~ /^#{NON_ATTR_ARG_KEY_PREFIX}action/
- # Replace quantifiers with default actions.
- arg_entities[1].gsub!(/\*/, DEFAULT_CONTROLLER_ACTIONS.join(','))
- arg_entities[1].gsub!(/new\+/, [:new, :create].join(','))
- arg_entities[1].gsub!(/edit\+/, [:edit, :update].join(','))
-
- arg_actions = arg_entities[1].split(',').compact.uniq
- @actions = arg_actions.collect { |action| action.downcase.to_sym }
- elsif arg =~ /^#{NON_ATTR_ARG_KEY_PREFIX}(format|respond_to)/
- # Replace quantifiers with default respond_to-formats.
- arg_entities[1].gsub!(/\*/, DEFAULT_RESPOND_TO_FORMATS.join(','))
-
- arg_formats = arg_entities[1].split(',').compact.uniq
- @formats = arg_formats.collect { |format| format.downcase.to_sym }
- elsif arg =~ /^#{NON_ATTR_ARG_KEY_PREFIX}index/
- @args_for_model << arg
- end
- else
- @attributes << Rails::Generator::GeneratedAttribute.new(*arg_entities)
- @args_for_model << arg
- end
- end
-
- @actions ||= DEFAULT_ARGS[:actions] || DEFAULT_CONTROLLER_ACTIONS
- @formats ||= DEFAULT_ARGS[:formats] || DEFAULT_RESPOND_TO_FORMATS
- @options = DEFAULT_OPTIONS.merge(options)
- end
-
- def manifest
- record do |m|
- # Check for class naming collisions.
- m.class_collisions "#{controller_class_name}Controller", "#{controller_class_name}ControllerTest"
- m.class_collisions "#{controller_class_name}Helper", "#{controller_class_name}HelperTest"
-
- # Controllers.
- controller_template = options[:resourceful] ? 'inherited_resources' : 'action'
- m.directory File.join(CONTROLLERS_PATH, controller_class_path)
- m.template File.join('controllers', "#{controller_template}_controller.rb"),
- File.join(CONTROLLERS_PATH, controller_class_path, "#{controller_file_name}_controller.rb")
-
- # Controller Tests.
- unless options[:skip_tests] || options[:skip_controller_tests]
- controller_tests_path = File.join(TEST_PATHS[test_framework], FUNCTIONAL_TESTS_PATH[test_framework])
- m.directory File.join(controller_tests_path, controller_class_path)
- m.template File.join('controllers', 'tests', "#{test_framework}", 'functional_test.rb'),
- File.join(controller_tests_path, controller_class_path, "#{controller_file_name}_controller_#{TEST_POST_FIX[test_framework]}.rb")
- end
-
- # Helpers.
- unless options[:skip_helpers]
- m.directory File.join(HELPERS_PATH, controller_class_path)
- m.template File.join('helpers', 'helper.rb'),
- File.join(HELPERS_PATH, controller_class_path, "#{controller_file_name}_helper.rb")
-
- # Helper Tests
- unless options[:skip_tests]
- helper_tests_path = File.join(TEST_PATHS[test_framework], 'helpers')
- m.directory File.join(helper_tests_path, controller_class_path)
- m.template File.join('helpers', 'tests', "#{test_framework}", 'unit_test.rb'),
- File.join(helper_tests_path, controller_class_path, "#{controller_file_name}_helper_#{TEST_POST_FIX[test_framework]}.rb")
- end
- end
-
- # Views.
- unless options[:skip_views]
- m.directory File.join(VIEWS_PATH, controller_class_path, controller_file_name)
- # View template for each action.
- (actions & ACTION_VIEW_TEMPLATES.keys).each do |action|
- m.template File.join('views', "#{view_template_format}", "#{action}.html.#{view_template_format}"),
- File.join(VIEWS_PATH, controller_file_name, "#{action}.html.#{view_template_format}")
-
- # View template for each partial - if not already copied.
- (ACTION_VIEW_TEMPLATES[action] || []).each do |partial|
- m.template File.join('views', "#{view_template_format}", "_#{partial}.html.#{view_template_format}"),
- File.join(VIEWS_PATH, controller_file_name, "_#{partial}.html.#{view_template_format}")
- end
- end
- end
-
- # Builders.
- unless options[:skip_builders]
- m.directory File.join(VIEWS_PATH, controller_class_path, controller_file_name)
- (actions & ACTION_FORMAT_BUILDERS.keys).each do |action|
- (formats & ACTION_FORMAT_BUILDERS[action] || []).each do |format|
- m.template File.join('views', 'builder', "#{action}.#{format}.builder"),
- File.join(VIEWS_PATH, controller_file_name, "#{action}.#{format}.builder")
- end
- end
- end
-
- # Layout.
- if options[:layout]
- m.directory File.join(LAYOUTS_PATH)
- m.template File.join('views', "#{view_template_format}", "layout.html.#{view_template_format}"),
- File.join(LAYOUTS_PATH, "#{controller_file_name}.html.#{view_template_format}")
- end
-
- # Routes.
- unless resource_route_exists?
- # TODO: Override Rails default method to not generate route if it's already defined.
- m.route_resources controller_file_name
- end
-
- # Models - use Rails default generator.
- m.dependency 'dry_model', [name] + @args_for_model, options.merge(:collision => :skip)
- end
- end
-
- ### Fixture/Factory Helpers.
-
- def build_object
- case options[:factory_framework]
- when :factory_girl then
- "Factory(:#{singular_name})"
- when :machinist then
- "#{class_name}.make"
- when :object_daddy then
- "#{class_name}.generate"
- else #:fixtures
- "#{table_name}(:basic)"
- end
- end
-
- ### Link Helpers.
-
- def collection_instance
- "@#{collection_name}"
- end
-
- def resource_instance
- "@#{singular_name}"
- end
-
- def index_path
- "#{collection_name}_path"
- end
-
- def new_path
- "new_#{singular_name}_path"
- end
-
- def show_path(object_name = resource_instance)
- "#{singular_name}_path(#{object_name})"
- end
-
- def edit_path(object_name = resource_instance)
- "edit_#{show_path(object_name)}"
- end
-
- def destroy_path(object_name = resource_instance)
- "#{object_name}"
- end
-
- def index_url
- "#{collection_name}_url"
- end
-
- def new_url
- "new_#{singular_name}_url"
- end
-
- def show_url(object_name = resource_instance)
- "#{singular_name}_url(#{object_name})"
- end
-
- def edit_url(object_name = resource_instance)
- "edit_#{show_url(object_name)}"
- end
-
- def destroy_url(object_name = resource_instance)
- "#{object_name}"
- end
-
- ### Feed Helpers.
-
- def feed_link(format)
- case format
- when :atom then
- ":href => #{plural_name}_url(:#{format}), :rel => 'self'"
- when :rss then
- "#{plural_name}_url(#{singular_name}, :#{format})"
- end
- end
-
- def feed_entry_link(format)
- case format
- when :atom then
- ":href => #{singular_name}_url(#{singular_name}, :#{format})"
- when :rss then
- "#{singular_name}_url(#{singular_name}, :#{format})"
- end
- end
-
- def feed_date(format)
- case format
- when :atom then
- "(#{collection_instance}.first.created_at rescue Time.now.utc).strftime('%Y-%m-%dT%H:%M:%SZ')"
- when :rss then
- "(#{collection_instance}.first.created_at rescue Time.now.utc).to_s(:rfc822)"
- end
- end
-
- def feed_entry_date(format)
- case format
- when :atom then
- "#{singular_name}.try(:updated_at).strftime('%Y-%m-%dT%H:%M:%SZ')"
- when :rss then
- "#{singular_name}.try(:updated_at).to_s(:rfc822)"
- end
- end
-
- protected
-
- def resource_route_exists?
- route_exp = "map.resources :#{controller_file_name}"
- File.read(ROUTES_FILE_PATH) =~ /(#{route_exp.strip}|#{route_exp.strip.tr('\'', '\"')})/
- end
-
- def assign_names!(name)
- super(name)
- @model_singular_name = @singular_name
- @model_plural_name = @plural_name
- @collection_name = options[:resourceful] ? RESOURCEFUL_COLLECTION_NAME : @model_plural_name
- @singular_name = options[:resourceful] ? RESOURCEFUL_SINGULAR_NAME : @model_singular_name
- @plural_name = options[:resourceful] ? RESOURCEFUL_SINGULAR_NAME.pluralize : @model_plural_name
- end
-
- def add_options!(opt)
- super(opt)
-
- ### CONTROLLER + VIEW + HELPER
-
- opt.separator ' '
- opt.separator 'Scaffold Options:'
-
- opt.on('--skip-resourceful',
- "Controller: Skip 'inherited_resources' style controllers and views. Requires gem 'josevalim-inherited_resources'.") do |v|
- options[:resourceful] = !v
- end
-
- opt.on('--skip-pagination',
- "Controller/View: Skip 'will_paginate' for collections in controllers and views. Requires gem 'mislav-will_paginate'.") do |v|
- options[:pagination] = !v
- end
- opt.on('--skip-search',
- "Controller/View: Skip 'searchlogic' for collections in controllers and views. Requires gem 'searchlogic'.") do |v|
- options[:search] = !v
- end
-
- opt.on('--skip-formtastic',
- "View: Skip 'formtastic' style forms. Requires gem 'justinfrench-formtastic'.") do |v|
- options[:formtastic] = !v
- end
-
- opt.on("--skip-controller-tests", "Controller: Skip generation of tests for controller.") do |v|
- options[:skip_controller_tests] = v
- end
- opt.on('--skip-views', "View: Skip generation of views.") do |v|
- options[:skip_views] = v
- end
-
- opt.on('--skip-builders', "View: Skip generation of builders.") do |v|
- options[:skip_builders] = v
- end
-
- opt.on('--layout', "View: Generate layout.") do |v|
- options[:layout] = v
- end
-
- opt.on('--skip-helper', "Helper: Skip generation of helpers.") do |v|
- options[:skip_helpers] = v
- end
-
- ### MODEL
-
- opt.separator ' '
- opt.separator 'Model Options:'
-
- opt.on("--skip-timestamps", "Model: Don't add timestamps to the migration file.") do |v|
- options[:skip_timestamps] = v
- end
-
- opt.on("--skip-migration", "Model: Skip generation of migration file.") do |v|
- options[:skip_migration] = v
- end
-
- opt.on("--skip-tests", "Model: Skip generation of tests.") do |v|
- options[:skip_tests] = v
- end
-
- opt.on("--skip-controller tests", "Controller: Skip generation of tests for controller.") do |v|
- options[:skip_controller_tests] = v
- end
-
- opt.separator ' '
- end
-
- def banner_args
- [BANNER_ARGS, super].flatten.join(' ')
- end
-
- def banner_options
- [BANNER_OPTIONS, super].flatten.join(' ')
- end
-
- def banner
- [super, banner_args, banner_options].join(' ')
- end
-
- end