Skip to content

Commit

Permalink
Make ActiveModel Validation reflection compatible with validation_ref…
Browse files Browse the repository at this point in the history
…lection

* Tests in rails3/rspec2 environment fail due to unstub! not being implemented in rspec-mocks
  (up to and including rspe-mocks 2.0.0.beta7)
  • Loading branch information
mjonuschat authored and Morton Jonuschat committed Apr 21, 2010
1 parent 2769784 commit 4a1c8b8
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 12 deletions.
25 changes: 18 additions & 7 deletions lib/formtastic.rb
Expand Up @@ -535,23 +535,34 @@ def strip_formtastic_options(options) #:nodoc:
# * if the :required option was provided in the options hash, the true/false value will be
# returned immediately, allowing the view to override any guesswork that follows:
#
# * if the :required option isn't provided in the options hash, and the object is an ActiveModel,
# true is returned
# * if the :required option isn't provided in the options hash, and the ValidationReflection
# plugin is installed (http://github.com/redinger/validation_reflection), or the object is
# an ActiveModel, true is returned
# if the validates_presence_of macro has been used in the class for this attribute, or false
# otherwise.
#
# * if the :required option isn't provided, and the object isn't an ActiveModel, the value of the
# * if the :required option isn't provided, and validates_presence_of can't be determined, the
# configuration option @@all_fields_required_by_default is used.
#
def method_required?(attribute) #:nodoc:
if @object && @object.class.respond_to?(:validators_on)
if @object && @object.class.respond_to?(:reflect_on_validations_for)
attribute_sym = attribute.to_s.sub(/_id$/, '').to_sym
!@object.class.validators_on(attribute_sym).find{|validator| (validator.kind == :presence) && (validator.options.present? ? options_require_validation?(validator.options) : true)}.nil?

@object.class.reflect_on_validations_for(attribute_sym).any? do |validation|
validation.macro == :validates_presence_of &&
validation.name == attribute_sym &&
(validation.options.present? ? options_require_validation?(validation.options) : true)
end
else
@@all_fields_required_by_default
if @object && @object.class.respond_to?(:validators_on)
attribute_sym = attribute.to_s.sub(/_id$/, '').to_sym
!@object.class.validators_on(attribute_sym).find{|validator| (validator.kind == :presence) && (validator.options.present? ? options_require_validation?(validator.options) : true)}.nil?
else
@@all_fields_required_by_default
end
end
end

# Determines whether the given options evaluate to true
def options_require_validation?(options) #nodoc
if_condition = !options[:if].nil?
Expand Down
120 changes: 117 additions & 3 deletions spec/input_spec.rb
Expand Up @@ -129,12 +129,126 @@

describe 'and an object was given' do

describe 'and the validation reflection plugin is available' do

before do
::Post.stub!(:reflect_on_validations_for).and_return([])
@new_post.class.stub!(:method_defined?).with(:reflect_on_validations_for).and_return(true)
end

after do
::Post.unstub(:reflect_on_validations_for)
@new_post.class.stub!(:method_defined?).with(:reflect_on_validations_for).and_return(false)
end

describe 'and validates_presence_of was called for the method' do
it 'should be required' do
@new_post.class.should_receive(:reflect_on_validations_for).with(:title).and_return([
mock('MacroReflection', :macro => :validates_presence_of, :name => :title, :options => nil)
])
@new_post.class.should_receive(:reflect_on_validations_for).with(:body).and_return([
mock('MacroReflection', :macro => :validates_presence_of, :name => :body, :options => {:if => true})
])

form = semantic_form_for(@new_post) do |builder|
concat(builder.input(:title))
concat(builder.input(:body))
end
output_buffer.concat(form) if defined?(ActiveSupport::SafeBuffer)
output_buffer.should have_tag('form li.required')
output_buffer.should_not have_tag('form li.optional')
end

it 'should be not be required if the optional :if condition is not satisifed' do
should_be_required(:required => false, :options => { :if => false })
end

it 'should not be required if the optional :if proc evaluates to false' do
should_be_required(:required => false, :options => { :if => proc { |record| false } })
end

it 'should be required if the optional :if proc evaluates to true' do
should_be_required(:required => true, :options => { :if => proc { |record| true } })
end

it 'should not be required if the optional :unless proc evaluates to true' do
should_be_required(:required => false, :options => { :unless => proc { |record| true } })
end

it 'should be required if the optional :unless proc evaluates to false' do
should_be_required(:required => true, :options => { :unless => proc { |record| false } })
end

it 'should be required if the optional :if with a method string evaluates to true' do
@new_post.should_receive(:required_condition).and_return(true)
should_be_required(:required => true, :options => { :if => :required_condition })
end

it 'should be required if the optional :if with a method string evaluates to false' do
@new_post.should_receive(:required_condition).and_return(false)
should_be_required(:required => false, :options => { :if => :required_condition })
end

it 'should not be required if the optional :unless with a method string evaluates to false' do
@new_post.should_receive(:required_condition).and_return(false)
should_be_required(:required => true, :options => { :unless => :required_condition })
end

it 'should be required if the optional :unless with a method string evaluates to true' do
@new_post.should_receive(:required_condition).and_return(true)
should_be_required(:required => false, :options => { :unless => :required_condition })
end
end

# TODO make a matcher for this?
def should_be_required(options)
@new_post.class.should_receive(:reflect_on_validations_for).with(:body).and_return([
mock('MacroReflection', :macro => :validates_presence_of, :name => :body, :options => options[:options])
])

form = semantic_form_for(@new_post) do |builder|
concat(builder.input(:body))
end

output_buffer.concat(form) if defined?(ActiveSupport::SafeBuffer)

if options[:required]
output_buffer.should_not have_tag('form li.optional')
output_buffer.should have_tag('form li.required')
else
output_buffer.should have_tag('form li.optional')
output_buffer.should_not have_tag('form li.required')
end
end

describe 'and validates_presence_of was not called for the method' do
before do
@new_post.class.should_receive(:reflect_on_validations_for).with(:title).and_return([])
end

it 'should not be required' do
form = semantic_form_for(@new_post) do |builder|
concat(builder.input(:title))
end
output_buffer.concat(form) if defined?(ActiveSupport::SafeBuffer)
output_buffer.should_not have_tag('form li.required')
output_buffer.should have_tag('form li.optional')
end
end

end

describe 'and its a ActiveModel' do

before do
::Post.stub!(:validators_on).and_return([])
@new_post.class.stub!(:method_defined?).with(:validators_on).and_return(true)
end

after do
::Post.unstub!(:validators_on)
@new_post.class.stub!(:method_defined?).with(:validators_on).and_return(false)
end
describe 'and validates_presence_of was called for the method' do
it 'should be required' do

Expand Down Expand Up @@ -432,7 +546,7 @@ def should_be_required(options)
end

output_buffer.concat(form) if defined?(ActiveSupport::SafeBuffer)
output_buffer.should have_tag('form li label', @localized_label_text)
output_buffer.should have_tag('form li label', Regexp.new('^' + @localized_label_text))
end
end
end
Expand Down Expand Up @@ -507,7 +621,7 @@ def should_be_required(options)
concat(builder.input(:published, :as => :boolean, :label => true))
end
output_buffer.concat(form) if defined?(ActiveSupport::SafeBuffer)
output_buffer.should have_tag('form li label', @localized_label_text)
output_buffer.should have_tag('form li label', Regexp.new('^' + @localized_label_text))
end

it 'should render a hint paragraph containing an optional localized label (I18n) if first is not set' do
Expand All @@ -525,7 +639,7 @@ def should_be_required(options)
concat(builder.input(:published, :as => :boolean, :label => true))
end
output_buffer.concat(form) if defined?(ActiveSupport::SafeBuffer)
output_buffer.should have_tag('form li label', @default_localized_label_text)
output_buffer.should have_tag('form li label', Regexp.new('^' + @default_localized_label_text))
end
end
end
Expand Down
2 changes: 0 additions & 2 deletions spec/spec_helper.rb
Expand Up @@ -174,7 +174,6 @@ def new_author_path; "/authors/new"; end
::Author.stub!(:find).and_return([@fred, @bob])
::Author.stub!(:human_attribute_name).and_return { |column_name| column_name.humanize }
::Author.stub!(:human_name).and_return('::Author')
::Author.stub!(:validators_on).and_return([])
::Author.stub!(:reflect_on_association).and_return { |column_name| mock('reflection', :options => {}, :klass => Post, :macro => :has_many) if column_name == :posts }
::Author.stub!(:content_columns).and_return([mock('column', :name => 'login'), mock('column', :name => 'created_at')])
::Author.stub!(:to_key).and_return(nil)
Expand Down Expand Up @@ -211,7 +210,6 @@ def new_author_path; "/authors/new"; end
::Post.stub!(:human_attribute_name).and_return { |column_name| column_name.humanize }
::Post.stub!(:human_name).and_return('Post')
::Post.stub!(:reflect_on_all_validations).and_return([])
::Post.stub!(:validators_on).and_return([])
::Post.stub!(:reflect_on_association).and_return do |column_name|
case column_name
when :author, :author_status
Expand Down

0 comments on commit 4a1c8b8

Please sign in to comment.