Skip to content
Browse files

Merge pull request #721 from jcorcuera/GH-717

FormGenerator is Back! (should close #717)
  • Loading branch information...
2 parents e95ad93 + 1fbdf8f commit e767ce761df1047c3d6577e662d15a51f9235f56 @justinfrench justinfrench committed Oct 24, 2011
View
105 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
View
4 lib/generators/templates/_form.html.haml
@@ -1,8 +1,8 @@
= semantic_form_for @<%= singular_name %> do |f|
= f.inputs do
- <% attributes.each do |attribute| -%>
+ <%- attributes.each do |attribute| -%>
= f.input :<%= attribute.name %>
- <% end -%>
+ <%- end -%>
= f.buttons do
= f.commit_button
View
4 lib/generators/templates/_form.html.slim
@@ -1,8 +1,8 @@
= semantic_form_for @<%= singular_name %> do |f|
= f.inputs do
- <% attributes.each do |attribute| -%>
+ <%- attributes.each do |attribute| -%>
= f.input :<%= attribute.name %>
- <% end -%>
+ <%- end -%>
= f.buttons do
= f.commit_button
View
118 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 e767ce7

Please sign in to comment.
Something went wrong with that request. Please try again.