Judge not validating email field
jeffreyguenther opened this issue · 5 comments
I can't seem to get judge to validate my email field. If I turn off validation the field the form validates and can be submit. I have stopped using devise's :validatable
module because of some of the custom logic I'm doing around validation. Judge seems to be responding to the uniques query fine. The user doesn't exist.
Started GET "/judge?klass=user&attribute=email&value=bob%40example.com&kind=uniqueness" for 127.0.0.1 at 2014-10-09 20:54:49 -0700
Processing by Judge::ValidationsController#build as JSON
Parameters: {"klass"=>"user", "attribute"=>"email", "value"=>"bob@example.com", "kind"=>"uniqueness"}
User Exists (12.1ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = 'bob@example.com' LIMIT 1
Completed 200 OK in 17ms (Views: 0.1ms | ActiveRecord: 12.1ms)
Model:
class User < ActiveRecord::Base
validates :email, presence: true
validates :email, uniqueness: true
validates :email, format: { with: /.+@.+/, message: "must contain \"@\""}
with_options if: :should_validate? do |admin|
admin.validates :password, presence: true
admin.validates :password, confirmation: true
admin.validates :password, length: { in: 8..128 }
admin.validates :first_name, presence: true
admin.validates :last_name, presence: true
end
def should_validate?
admin? || persisted?
end
end
controls = {}
$(".participate-form__form-section").each (i, e) ->
# console.log "building collection of controls"
# get the section's inputs
id = "fs-#{i + 1}"
# console.log id
inputs = document.querySelectorAll(".participate-form__form-section##{id} input[type=text], .participate-form__form-section##{id} input[type=email]")
controls["#{id}"] = inputs
# On click, show the form section referenced in the href
$(".next-btn").on "click", (e) ->
e.preventDefault()
isValid = false
# Add logic to manage the validations per sheet
id = $(this).parent().attr("id")
isValid = validate_section(controls[id])
console.log "section is valid: #{isValid}"
if isValid
#do some stuff to prompt the user
validate_section = (controls) ->
console.log controls
results = []
for control in controls
results.push(validate_field(control))
console.log results
!(results.indexOf(false) >= 0)
validate_field = (control)->
# console.log id
isValid = false
judge.validate(control,
valid: (element) ->
# console.log "valid"
isValid = true
invalid: (element, messages) ->
id = element.getAttribute("id")
# console.log "invalid"
console.log messages
$("##{id}").popover({
# trigger: "manual"
# html : true,
placement: "left",
content: messages
}).popover('show')
$("##{id}").toggleClass("invalid-field", true)
)
console.log "#{control.getAttribute("id")} is #{isValid}"
isValid
The generated HTML is:
<input class="participate-form__input-field" data-validate="[{"kind":"presence","options":{},"messages":{"blank":"can't be blank"}},
{"kind":"uniqueness","options":{},"messages":{}},
{"kind":"format","options":{"with":"(?-mix:.+@.+)","message":"must contain \"@\""},"messages":{"invalid":"must contain \"@\"","blank":"can't be blank"}}]"
id="user_email"
name="user[email]"
placeholder="jsmith@abc.com"
type="email" value="bob@example.com">
Any idea what the issue might be?
Accidentally closed the issue. I'm still having the issue.
Starting to get a little closer to the cause. It appears the callbacks are never being called for uniqueness tests
judge.validate(control,
valid: (element) ->
console.log "valid"
invalid: (element, messages) ->
console.log "invalid"
)
And the issue was.... judge.validate
is an asynchronous method. The code above doesn't take that into account!
@jrguenther how do you take this into account?
When judge validates for uniqueness, it makes an AJAX call to determine if the email already exists.
My mistake was to think of the validate as a synchronous method and my code would run in sequence. You'll notice I have a loop above to validate a section of a form. Each section contains several controls. Because validate is called once for every control in the loop, I was assuming the validate
calls would return in the same sequence and I would be able to get the result in the loop. Instead, controls were returning invalid when in fact they were valid because they were returning valid after my code had moved on.
To solve the problem, I had to use a jquery deferred to provide a callback.
(".next-btn").on "click", (e) ->
e.preventDefault()
isValid = false
#controls is an object with an array of controls per id. Each id represents a section of the form.
validate_section(controls[id]).done (result) ->
console.log result
$(".participate-form__content").animate({
scrollTop: target.position().top * offset
}, 1000);
validate_section = (controls) ->
console.log controls
count = controls.length
result = true
deferred = $.Deferred()
for control in controls
validate_field(control).done((isValid) ->
console.log "Control is validated"
count--
result = result && isValid
console.log "#Result: #{result}"
console.log "Controls left: #{count}"
if(count == 0 && result)
deferred.resolve(result)
console.log "Section is good"
)
deferred.promise()
validate_field = (control)->
deferred = $.Deferred()
# add spinny part
if control.getAttribute("type") is "email"
$(".has-feedback").append("<i class=\"form-control-feedback fa fa-circle-o-notch fa-spin\"></i>")
judge.validate(control,
valid: (element) ->
console.log "called valid()"
id = element.getAttribute("id")
$("##{id}").toggleClass("invalid-field", false)
deferred.resolve(true)
invalid: (element, messages) ->
id = element.getAttribute("id")
console.log "called invalid()"
console.log messages
# Create html error messages
html_message = messages.join("<br>")
# Create a new pop over
$("##{id}").popover("destroy")
$("##{id}").popover({
# trigger: "manual"
html : true,
placement: "left",
content: html_message
}).popover('show')