refinery/refinerycms

View on GitHub
core/lib/generators/refinery/cms/cms_generator.rb

Summary

Maintainability
C
1 day
Test Coverage
require 'pathname'
require 'mkmf'

module Refinery
  class CmsGenerator < Rails::Generators::Base
    source_root Pathname.new(File.expand_path('../templates', __FILE__))

    class_option :update,  :type => :boolean, :aliases => nil, :group => :runtime,
                           :desc => "Update an existing Refinery CMS based application"
    class_option :fresh_installation, :type => :boolean, :aliases => nil, :group => :runtime, :default => false,
                           :desc => "Allow Refinery to remove default Rails files in a fresh installation"
    class_option :heroku,  :type => :string, :default => nil, :group => :runtime, :banner => 'APP_NAME',
                           :desc => "Deploy to Heroku after the generator has run."
    class_option :stack,   :type => :string, :default => 'cedar-14', :group => :runtime,
                           :desc => "Specify which Heroku stack you want to use. Requires --heroku option to function."
    class_option :skip_db, :type => :boolean, :default => false, :aliases => nil, :group => :runtime,
                           :desc => "Skip over any database creation, migration or seeding."
    class_option :skip_migrations, :type => :boolean, :default => false, :aliases => nil, :group => :runtime,
                           :desc => "Skip over installing or running migrations."

    def generate
      start_pretending?

      manage_roadblocks! unless self.options[:update]

      ensure_environments_are_sane!

      stop_pretending?

      append_gemfile!

      append_gitignore!

      append_asset_pipeline!

      forced_overwriting?

      copy_files!

      create_decorators!

      mount!

      run_additional_generators! if self.options[:fresh_installation]

      prepare_database!

      deploy_to_hosting?
    end

    protected

    def append_asset_pipeline!
      application_css = 'app/assets/stylesheets/application.css'
      if destination_path.join(application_css).file?
        insert_into_file application_css, %q{*= require refinery/formatting
 *= require refinery/theme
 },      :before => "*= require_self"
      end
    end

    def append_gemfile!
      if destination_path.join('Gemfile').file? &&
         destination_path.join('Gemfile').read !~ %r{group :development, :test do\n.+?gem 'sqlite3'\nend}m
        gsub_file 'Gemfile', %q{gem 'sqlite3'}, %q{group :development, :test do
  gem 'sqlite3'
end}  end
    end

    def append_gitignore!
      # Ensure .gitignore exists and append our rules to it.
      create_file ".gitignore" unless destination_path.join('.gitignore').file?
      our_ignore_rules = self.class.source_root.join('.gitignore').read
      our_ignore_rules = our_ignore_rules.split('# REFINERY CMS DEVELOPMENT').first if destination_path != Refinery.root
      append_file ".gitignore", our_ignore_rules
    end

    def append_heroku_gems!
      production_gems = [
        "gem 'dragonfly-s3_data_store'"
      ]
      production_gems << "gem 'puma'" unless destination_gemfile_has_puma?
      production_gems << "gem 'pg'" unless destination_gemfile_has_postgres?

      append_file "Gemfile", %Q{
# The Ruby version is specified here so that Heroku uses the right version.
ruby #{current_ruby_version.inspect}

# Gems that have been added for Heroku support
group :production do
  {{production_gems}}
end
}.sub("{{production_gems}}", production_gems.join("\n  "))
    end

    def current_ruby_version
      ENV['RUBY_VERSION'].presence || RUBY_VERSION
    end

    def bundle!
      run 'bundle install'
    end

    def copy_files!
      # The extension installer only installs database templates.
      Pathname.glob(self.class.source_root.join('**', '*')).reject{ |f|
        f.directory? or f.to_s =~ /\/db\//
      }.sort.each do |path|
        copy_file path, path.to_s.gsub(self.class.source_root.to_s, destination_path.to_s)
      end
    end

    def create_decorators!
      # Create decorator directories
      %w[controllers models].each do |decorator_namespace|
        src_file_path = "app/decorators/#{decorator_namespace}/refinery/.keep"
        copy_file self.class.source_root.join(src_file_path), destination_path.join(src_file_path)
      end
    end

    def create_heroku_procfile!
      create_file "Procfile" do
        "web: bundle exec puma -C config/puma.rb"
      end unless destination_path.join('Procfile').file?

      create_file "config/puma.rb" do
%{threads Integer(ENV['MIN_THREADS']  || 1), Integer(ENV['MAX_THREADS'] || 16)

workers Integer(ENV['PUMA_WORKERS'] || 3)

rackup DefaultRackup
port ENV['PORT'] || 3000
environment ENV['RACK_ENV'] || 'development'
preload_app!

on_worker_boot do
  # worker specific setup
  ActiveSupport.on_load(:active_record) do
    ActiveRecord::Base.establish_connection
  end
end
}
      end unless destination_path.join('config', 'puma.rb').file?
    end

    def deploy_to_hosting?
      if heroku?
        if heroku_toolbelt_missing?
          fail <<-ERROR
  \033[31m[ABORTING]\033[0m Heroku Toolbelt is not installed. Please re-start the installer after installing at:\nhttps://devcenter.heroku.com/articles/heroku-command-line
  ERROR
        end

        append_heroku_gems!

        # Sanity check the heroku application name and save whatever messages are produced.
        message = sanity_check_heroku_application_name!

        create_heroku_procfile!

        bundle!

        # Supply the deploy process with the previous messages to make them visible.
        deploy_to_hosting_heroku!(message)
      end
    end

    def deploy_to_hosting_heroku!(message = nil)
      say_status "Initializing and committing to git..", nil
      run "git init && git add . && git commit -am 'Created application using Refinery CMS #{Refinery.version}'"

      say_status message, nil, :yellow if message

      say_status "Creating Heroku app..", nil
      run ["heroku create",
           (options[:heroku] if heroku?),
           "#{"--stack #{options[:stack]}" if options[:stack]}"
          ].compact.join(' ')

      say_status "Pushing to Heroku (this takes time, be patient)..", nil
      run "git push heroku master"

      say_status "Setting up the Heroku database..", nil
      run "heroku#{' run' if options[:stack] == 'cedar-14'} rake db:migrate"

      say_status "Seeding the Heroku database..", nil
      run "heroku#{' run' if options[:stack] == 'cedar-14'} rake db:seed"

      say_status "Restarting servers...", nil
      run "heroku restart"
    end

    # Helper method to quickly convert destination_root to a Pathname for easy file path manipulation
    def destination_path
      @destination_path ||= Pathname.new(self.destination_root)
    end

    def ensure_environments_are_sane!
      # Massage environment files
      %w(development test production).map{ |e| "config/environments/#{e}.rb"}.each do |env|
        next unless destination_path.join(env).file?

        # Refinery does not necessarily expect action_mailer to be available as
        # we may not always require it (currently only the authentication extension).
        # Rails, however, will optimistically place config entries for action_mailer.
        current_mailer_config = File.read(destination_path.join(env)).to_s.
                                     match(%r{^\s.+?config\.action_mailer\..+([\w\W]*\})?}).
                                     to_a.flatten.first

        if current_mailer_config.present?
          new_mailer_config = [
            "  if config.respond_to?(:action_mailer)",
            current_mailer_config.gsub(%r{\A\n+?}, ''). # remove extraneous newlines at the start
                                  gsub(%r{^\ \ }) { |line| "  #{line}" }, # add indentation on each line
            "  end"
          ].join("\n")

          gsub_file env, current_mailer_config, new_mailer_config, :verbose => false
        end

        gsub_file env, "config.assets.compile = false", "config.assets.compile = true", :verbose => false
      end
    end

    def forced_overwriting?
      force_options = self.options.dup
      force_options[:force] = self.options[:force] || self.options[:update]
      self.options = force_options
    end

    def destination_gemfile_has_postgres?
      destination_gemfile_has_gem?('pg')
    end

    def destination_gemfile_has_puma?
      destination_gemfile_has_gem?('puma')
    end

    def destination_gemfile_has_gem?(gem_name)
      destination_path.join('Gemfile').file? &&
        destination_path.join('Gemfile').read =~ %r{gem ['"]#{gem_name}['"]}
    end

    def heroku?
      options[:heroku].present?
    end

    def heroku_toolbelt_missing?
      find_executable("heroku").nil?
    end

    def manage_roadblocks!
      %w(public/index.html app/views/layouts/application.html.erb).each do |roadblock|
        if (roadblock_path = destination_path.join(roadblock)).file?
          if self.options[:fresh_installation]
            remove_file roadblock_path, :verbose => true
          else
            say_status :"-- You may need to remove '#{roadblock}' for Refinery to function properly --", nil, :yellow
          end
        end
      end
    end

    def prepare_database!
      unless self.options[:skip_migrations]
        command = %w[railties:install:migrations]
        unless self.options[:skip_db]
          command |= %w[db:create db:migrate]
          command |= %w[db:seed] unless self.options[:skip_migrations]
        end
        rake command.join(' ')
      end
    end

    def mount!
      if (routes_file = destination_path.join('config', 'routes.rb')).file? && (self.behavior == :revoke || (routes_file.read !~ %r{mount\ Refinery::Core::Engine}))
        # Append routes
        mount = %Q{
  # This line mounts Refinery's routes at the root of your application.
  # This means, any requests to the root URL of your application will go to Refinery::PagesController#home.
  # If you would like to change where this extension is mounted, simply change the
  # configuration option `mounted_path` to something different in config/initializers/refinery/core.rb
  #
  # We ask that you don't use the :as option here, as Refinery relies on it being the default of "refinery"
  mount Refinery::Core::Engine, at: Refinery::Core.mounted_path

}

        inject_into_file 'config/routes.rb', mount, after: ".routes.draw do"
      end
    end

    def run_additional_generators!
      generator_args = []
      generator_args << '--quiet' if self.options[:quiet]
      generator_args << '--skip-migrations' if self.options[:skip_migrations]
      Refinery::CoreGenerator.start generator_args
      Refinery::Authentication::DeviseGenerator.start generator_args if defined?(Refinery::Authentication::DeviseGenerator)
      Refinery::Dragonfly::DragonflyGenerator.start generator_args if defined?(Refinery::Dragonfly::DragonflyGenerator)
      Refinery::ResourcesGenerator.start generator_args if defined?(Refinery::ResourcesGenerator)
      Refinery::PagesGenerator.start generator_args if defined?(Refinery::PagesGenerator)
      Refinery::ImagesGenerator.start generator_args if defined?(Refinery::ImagesGenerator)
      Refinery::I18nGenerator.start generator_args if defined?(Refinery::I18nGenerator)
    end

    def sanity_check_heroku_application_name!
      if heroku? && options[:heroku].to_s.include?('_') || options[:heroku].to_s.length > 30
        message = ["\nThe application name '#{options[:heroku]}' that you specified is invalid for Heroku."]
        suggested_name = options[:heroku].dup.to_s
        if suggested_name.include?('_')
          message << "This is because it contains underscores which Heroku does not allow."
          suggested_name.gsub!(/_/, '-')
        end
        if suggested_name.length > 30
          message << "This is#{" also" unless suggested_name.nil?} because it is longer than 30 characters."
          suggested_name = suggested_name[0..29]
        end

        options[:heroku] = suggested_name

        message << "We have changed the name to '#{suggested_name}' for you, hope it suits you.\n"
        message.join("\n")
      end

      options[:heroku] = '' if options[:heroku] == 'heroku'
    end

    def start_pretending?
      # Only pretend to do the next actions if this is Refinery to stay DRY
      if destination_path == Refinery.root
        say_status :'-- pretending to make changes that happen in an actual installation --', nil, :yellow
        old_pretend = self.options[:pretend]
        new_options = self.options.dup
        new_options[:pretend] = true
        self.options = new_options
      end
    end

    def stop_pretending?
      # Stop pretending
      if destination_path == Refinery.root
        say_status :'-- finished pretending --', nil, :yellow
        new_options = self.options.dup
        new_options[:pretend] = old_pretend
        self.options = new_options
      end
    end
  end
end