Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
stuartc committed Apr 20, 2012
2 parents 3e3503b + 548b491 commit a2812dc
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 6 deletions.
20 changes: 18 additions & 2 deletions lib/client_side_validations/action_view/form_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,24 @@ def filter_validators(method, filters)
unfiltered_validators.delete(validator.first)
end
else
if (conditional = (validator.last[:if] || validator.last[:unless])) && conditional.is_a?(Symbol) && !conditional_method_is_change_method?(conditional, method)
unfiltered_validators.delete(validator.first)
if (conditional = (validator.last[:if] || validator.last[:unless]))
result = case conditional
when Symbol
if @object.respond_to?(conditional)
@object.send(conditional)
else
raise(ArgumentError, "unknown method called '#{conditional}'")
end
when String
eval(conditional)
when Proc
conditional.call(@object)
end

# :if was specified and result is false OR :unless was specified and result was true
if (validator.last[:if] && !result) || (validator.last[:unless] && result)
unfiltered_validators.delete(validator.first)
end
end
end
unfiltered_validators[validator.first].delete(:if) if unfiltered_validators[validator.first]
Expand Down
4 changes: 4 additions & 0 deletions lib/client_side_validations/active_model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ def client_side_hash(model, attribute)
{ :message => model.errors.generate_message(attribute, message_type, options) }.merge(options.except(*::ActiveModel::Errors::CALLBACKS_OPTIONS - [:allow_blank, :if, :unless]))
end

def copy_conditional_attributes(to, from)
[:if, :unless].each { |key| to[key] = from[key] if from[key].present? }
end

private

def message_type
Expand Down
2 changes: 2 additions & 0 deletions lib/client_side_validations/active_model/length.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ def client_side_hash(model, attribute)
end
end

copy_conditional_attributes(hash, options)

hash
end

Expand Down
2 changes: 2 additions & 0 deletions lib/client_side_validations/active_model/numericality.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ def client_side_hash(model, attribute)
end
end

copy_conditional_attributes(hash, options)

hash
end

Expand Down
6 changes: 5 additions & 1 deletion lib/client_side_validations/active_record/middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ def self.is_unique?(klass, attribute, value, params)

(params[:scope] || {}).each do |attribute, value|
value = type_cast_value(klass, attribute, value)
relation = relation.and(t[attribute].eq(value))
if relation.is_a?(Arel::Nodes::SqlLiteral)
relation = Arel::Nodes::SqlLiteral.new("#{relation} AND #{t[attribute].eq(value).to_sql}")
else
relation = relation.and(t[attribute].eq(value))
end
end

!klass.where(relation).exists?
Expand Down
185 changes: 182 additions & 3 deletions test/action_view/cases/test_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -355,12 +355,12 @@ def test_time_zone_select
assert_equal expected, output_buffer
end

def test_conditional_validators_should_be_filtered
def test_conditional_validators_should_be_filtered_based_on_symbol_condition_true
hash = {
:cost => {
:presence => {
:message => "can't be blank",
:unless => :do_not_validate?
:unless => :do_validate?
}
},
:title => {
Expand All @@ -380,9 +380,182 @@ def test_conditional_validators_should_be_filtered
concat f.text_field(:title)
end

validators = {}
validators = {
'post[title]' => {:presence => {:message => "can't be blank"}}
}
expected = whole_form("/posts/123", "edit_post_123", "edit_post", :method => "put", :validators => validators) do
%{<input id="post_cost" name="post[cost]" size="30" type="text" />} +
%{<input data-validate="true" id="post_title" name="post[title]" size="30" type="text" />}
end
assert_equal expected, output_buffer
end

def test_conditional_validators_should_be_filtered_based_on_symbol_condition_false
hash = {
:cost => {
:presence => {
:message => "can't be blank",
:unless => :do_not_validate?
}
},
:title => {
:presence => {
:message => "can't be blank",
:if => :do_not_validate?
}
}
}

@post.title = nil
@post.stubs(:do_not_validate?).returns(false)
@post.stubs(:do_validate?).returns(true)
@post.stubs(:client_side_validation_hash).returns(hash)
form_for(@post, :validate => true) do |f|
concat f.text_field(:cost)
concat f.text_field(:title)
end

validators = {
'post[cost]' => {:presence => {:message => "can't be blank"}}
}
expected = whole_form("/posts/123", "edit_post_123", "edit_post", :method => "put", :validators => validators) do
%{<input data-validate="true" id="post_cost" name="post[cost]" size="30" type="text" />} +
%{<input id="post_title" name="post[title]" size="30" type="text" />}
end
assert_equal expected, output_buffer
end

def test_conditional_validators_should_be_filtered_based_on_string_condition_true
hash = {
:cost => {
:presence => {
:message => "can't be blank",
:unless => 'true'
}
},
:title => {
:presence => {
:message => "can't be blank",
:if => 'true'
}
}
}

@post.title = nil
@post.stubs(:client_side_validation_hash).returns(hash)
form_for(@post, :validate => true) do |f|
concat f.text_field(:cost)
concat f.text_field(:title)
end

validators = {
'post[title]' => {:presence => {:message => "can't be blank"}}
}
expected = whole_form("/posts/123", "edit_post_123", "edit_post", :method => "put", :validators => validators) do
%{<input id="post_cost" name="post[cost]" size="30" type="text" />} +
%{<input data-validate="true" id="post_title" name="post[title]" size="30" type="text" />}
end
assert_equal expected, output_buffer
end

def test_conditional_validators_should_be_filtered_based_on_string_condition_false
hash = {
:cost => {
:presence => {
:message => "can't be blank",
:unless => 'false'
}
},
:title => {
:presence => {
:message => "can't be blank",
:if => 'false'
}
}
}

@post.title = nil
@post.stubs(:client_side_validation_hash).returns(hash)
form_for(@post, :validate => true) do |f|
concat f.text_field(:cost)
concat f.text_field(:title)
end

validators = {
'post[cost]' => {:presence => {:message => "can't be blank"}}
}
expected = whole_form("/posts/123", "edit_post_123", "edit_post", :method => "put", :validators => validators) do
%{<input data-validate="true" id="post_cost" name="post[cost]" size="30" type="text" />} +
%{<input id="post_title" name="post[title]" size="30" type="text" />}
end
assert_equal expected, output_buffer
end

def test_conditional_validators_should_be_filtered_based_on_proc_condition_true
hash = {
:cost => {
:presence => {
:message => "can't be blank",
:unless => Proc.new { |o| o.do_validate? }
}
},
:title => {
:presence => {
:message => "can't be blank",
:if => Proc.new { |o| o.do_validate? }
}
}
}

@post.title = nil
@post.stubs(:do_not_validate?).returns(false)
@post.stubs(:do_validate?).returns(true)
@post.stubs(:client_side_validation_hash).returns(hash)
form_for(@post, :validate => true) do |f|
concat f.text_field(:cost)
concat f.text_field(:title)
end

validators = {
'post[title]' => {:presence => {:message => "can't be blank"}}
}
expected = whole_form("/posts/123", "edit_post_123", "edit_post", :method => "put", :validators => validators) do
%{<input id="post_cost" name="post[cost]" size="30" type="text" />} +
%{<input data-validate="true" id="post_title" name="post[title]" size="30" type="text" />}
end
assert_equal expected, output_buffer
end

def test_conditional_validators_should_be_filtered_based_on_proc_condition_false
hash = {
:cost => {
:presence => {
:message => "can't be blank",
:unless => Proc.new { |o| o.do_not_validate? }
}
},
:title => {
:presence => {
:message => "can't be blank",
:if => Proc.new { |o| o.do_not_validate? }
}
}
}

@post.title = nil
@post.stubs(:do_not_validate?).returns(false)
@post.stubs(:do_validate?).returns(true)
@post.stubs(:client_side_validation_hash).returns(hash)
form_for(@post, :validate => true) do |f|
concat f.text_field(:cost)
concat f.text_field(:title)
end

validators = {
'post[cost]' => {:presence => {:message => "can't be blank"}}
}
expected = whole_form("/posts/123", "edit_post_123", "edit_post", :method => "put", :validators => validators) do
%{<input data-validate="true" id="post_cost" name="post[cost]" size="30" type="text" />} +
%{<input id="post_title" name="post[title]" size="30" type="text" />}
end
assert_equal expected, output_buffer
Expand Down Expand Up @@ -579,6 +752,9 @@ def test_conditional_validator_ignored_when_using_changed_helpers
}

@post.title = nil
# we don't have _changed? methods by default so we must stub them
@post.stubs(:title_changed?).returns(true)
@post.stubs(:cost_changed?).returns(false)
@post.stubs(:client_side_validation_hash).returns(hash)
form_for(@post, :validate => true) do |f|
concat f.text_field(:cost)
Expand Down Expand Up @@ -613,6 +789,9 @@ def test_conditional_validator_ignored_when_using_changed_helpers_and_forcing_va
}

@post.title = nil
# we don't have _changed? methods by default so we must stub them
@post.stubs(:title_changed?).returns(true)
@post.stubs(:cost_changed?).returns(false)
@post.stubs(:client_side_validation_hash).returns(hash)
form_for(@post, :validate => true) do |f|
concat f.text_field(:cost, :validate => true)
Expand Down
18 changes: 18 additions & 0 deletions test/active_record/cases/test_middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,24 @@ def test_mysql_adapter_uniqueness_when_id_is_given
assert_equal 'true', last_response.body
end

def test_mysql_adapter_uniqueness_when_id_is_given_with_scope
user = User.create(:email => 'user@test.com', :name => 'Brian')
ActiveRecord::ConnectionAdapters::SQLite3Adapter.
any_instance.expects(:instance_variable_get).
with("@config").
returns({:adapter => "mysql"})

sql_without_binary = "#{User.arel_table["email"].eq(user.email).to_sql} AND #{User.arel_table.primary_key.not_eq(user.id).to_sql} AND #{User.arel_table["name"].eq(user.name).to_sql}"
relation = Arel::Nodes::SqlLiteral.new("BINARY #{sql_without_binary}")

#NOTE: Stubs User#where because SQLite3 don't know BINARY
result = User.where(sql_without_binary)
User.expects(:where).with(relation).returns(result)

get '/validators/uniqueness', { 'user[email]' => user.email, 'scope' => {'name' => user.name}, 'case_sensitive' => true, 'id' => user.id}
assert_equal 'true', last_response.body
end

def test_uniqueness_when_scope_is_given
User.create(:email => 'user@test.com', :age => 25)
get '/validators/uniqueness', { 'user[email]' => 'user@test.com', 'scope' => { 'age' => 30 }, 'case_sensitive' => true }
Expand Down

0 comments on commit a2812dc

Please sign in to comment.