Skip to content

Commit

Permalink
FormGenerator is Back!
Browse files Browse the repository at this point in the history
It is based on the old FormGenerator and keeps the same configuration options.
  • Loading branch information
jcorcuera committed Oct 18, 2011
1 parent dbb6e92 commit c546844
Show file tree
Hide file tree
Showing 2 changed files with 223 additions and 0 deletions.
105 changes: 105 additions & 0 deletions lib/generators/formtastic/form/form_generator.rb
@@ -0,0 +1,105 @@
# encoding: utf-8
module Formtastic
# Generates a Formtastic form partial based on an existing model. It will not overwrite existing
# files without confirmation.
#
# @example
# $ rails generate formtastic:form Post
# @example Copy the partial code to the pasteboard rather than generating a partial
# $ rails generate formtastic:form Post --copy
# @example Return HAML output instead of default template engine
# $ rails generate formtastic:form Post --haml
# @example Generate a form for specific model attributes
# $ rails generate formtastic:form Post title:string body:text
# @example Generate a form for a specific controller
# $ rails generate formtastic:form Post --controller admin/posts
class FormGenerator < Rails::Generators::NamedBase
desc "Generates a Formtastic form partial based on an existing model."

argument :name, :type => :string, :required => true, :banner => 'MODEL_NAME'
argument :attributes, :type => :array, :default => [], :banner => 'attribute attribute'

source_root File.expand_path('../../../templates', __FILE__)

class_option :template_engine

class_option :copy, :type => :boolean, :default => false, :group => :formtastic,
:desc => 'Copy the generated code the clipboard instead of generating a partial file."'

class_option :controller, :type => :string, :default => false, :group => :formtastic,
:desc => 'Generate for custom controller/view path - in case model and controller namespace is different, i.e. "admin/posts"'

def create_or_show
@attributes = reflected_attributes if @attributes.empty?

engine = options[:template_engine]

if options[:copy]
template = File.read("#{self.class.source_root}/_form.html.#{engine}")
erb = ERB.new(template, nil, '-')
generated_code = erb.result(binding).strip rescue nil
puts "The following code has been copied to the clipboard, just paste it in your views:" if save_to_clipboard(generated_code)
puts generated_code || "Error: Nothing generated. Does the model exist?"
else
empty_directory "app/views/#{controller_path}"
template "_form.html.#{engine}", "app/views/#{controller_path}/_form.html.#{engine}"
end
end

protected

def controller_path
@controller_path ||= if options[:controller]
options[:controller].underscore
else
name.underscore.pluralize
end
end

def reflected_attributes
columns = content_columns
columns += association_columns
end

def model
@model ||= name.camelize.constantize
end

# Collects content columns (non-relation columns) for the current class.
# Skips Active Record Timestamps.
def content_columns
model.content_columns.select do |column|
!Formtastic::Helpers::InputsHelper::SKIPPED_COLUMNS.include? column.name.to_sym
end
end

# Collects association columns (relation columns) for the current class. Skips polymorphic
# associations because we can't guess which class to use for an automatically generated input.
def association_columns
model.reflect_on_all_associations(:belongs_to).select do |association_reflection|
association_reflection.options[:polymorphic] != true
end
end

def save_to_clipboard(data)
return unless data

begin
case RUBY_PLATFORM
when /win32/
require 'win32/clipboard'
::Win32::Clipboard.data = data
when /darwin/ # mac
`echo "#{data}" | pbcopy`
else # linux/unix
`echo "#{data}" | xsel --clipboard` || `echo "#{data}" | xclip`
end
rescue LoadError
false
else
true
end
end

end
end
118 changes: 118 additions & 0 deletions spec/generators/formtastic/form/form_generator_spec.rb
@@ -0,0 +1,118 @@
require 'spec_helper'

# Generators are not automatically loaded by Rails
require 'generators/formtastic/form/form_generator'

describe Formtastic::FormGenerator do

include FormtasticSpecHelper

# Tell the generator where to put its output (what it thinks of as Rails.root)
destination File.expand_path("../../../../../tmp", __FILE__)

before do
@output_buffer = ''
prepare_destination
mock_everything
::Post.stub!(:reflect_on_all_associations).with(:belongs_to).and_return([
mock('reflection', :name => :author, :options => {}, :klass => ::Author, :macro => :belongs_to),
mock('reflection', :name => :reviewer, :options => {:class_name => 'Author'}, :klass => ::Author, :macro => :belongs_to),
mock('reflection', :name => :main_post, :options => {}, :klass => ::Post, :macro => :belongs_to),
mock('reflection', :name => :attachment, :options => {:polymorphic => true}, :macro => :belongs_to),
])
end

describe 'without model' do
it 'should raise Thor::RequiredArgumentMissingError' do
lambda { run_generator }.should raise_error(Thor::RequiredArgumentMissingError)
end
end

describe 'with existing model' do
it 'should not raise an exception' do
lambda { run_generator %w(Post) }.should_not raise_error(Thor::RequiredArgumentMissingError)
end
end

describe 'with attributes' do
before { run_generator %w(Post title:string author:references) }

describe 'render only the specified attributes' do
subject { file('app/views/posts/_form.html.erb') }
it { should exist }
it { should contain "<%= f.input :title %>" }
it { should contain "<%= f.input :author %>" }
it { should_not contain "<%= f.input :main_post %>" }
end
end

describe 'without attributes' do
before { run_generator %w(Post) }

subject { file('app/views/posts/_form.html.erb') }

describe 'content_columns' do
it { should contain "<%= f.input :title %>" }
it { should contain "<%= f.input :body %>" }
it { should_not contain "<%= f.input :created_at %>" }
it { should_not contain "<%= f.input :updated_at %>" }
end

describe 'reflection_on_association' do
it { should contain "<%= f.input :author %>" }
it { should contain "<%= f.input :reviewer %>" }
it { should contain "<%= f.input :main_post %>" }
it { should_not contain "<%= f.input :attachment %>" }
end
end

describe 'with template engine option' do
describe 'erb' do
before { run_generator %w(Post --template-engine erb) }

describe 'app/views/posts/_form.html.erb' do
subject { file('app/views/posts/_form.html.erb') }
it { should exist }
it { should contain "<%= semantic_form_for @post do |f| %>" }
end
end

describe 'haml' do
before { run_generator %w(Post --template-engine haml) }

describe 'app/views/posts/_form.html.haml' do
subject { file('app/views/posts/_form.html.haml') }
it { should exist }
it { should contain "= semantic_form_for @post do |f|" }
end
end

describe 'slim' do
before { run_generator %w(Post --template-engine slim) }

describe 'app/views/posts/_form.html.slim' do
subject { file('app/views/posts/_form.html.slim') }
it { should exist }
it { should contain "= semantic_form_for @post do |f|" }
end
end
end

describe 'with copy option' do
before { run_generator %w(Post --copy) }

describe 'app/views/posts/_form.html.erb' do
subject { file('app/views/posts/_form.html.erb') }
it { should_not exist }
end
end

describe 'with controller option' do
before { run_generator %w(Post --controller admin/posts) }

describe 'app/views/admin/posts/_form.html.erb' do
subject { file('app/views/admin/posts/_form.html.erb') }
it { should exist }
end
end
end

0 comments on commit c546844

Please sign in to comment.