Skip to content

Instantly share code, notes, and snippets.

@niklas
Created October 30, 2012 17:50
Show Gist options
  • Save niklas/3981832 to your computer and use it in GitHub Desktop.
Save niklas/3981832 to your computer and use it in GitHub Desktop.
HowTo create a DS.Model from ember-data, handling server-side validation errors
# If we save a record using ember-data's RESTadapter, and it fails, Rails
# returns the validation errors of the model as JSON hash:
#
# {"errors":{"name":["may not be blank"]}}
#
# This patches the RESTadapter to add these errors to the invalid record. It
# can be removed when the following Pull Request was merged into ember-data:
# https://github.com/emberjs/data/pull/376
DS.RESTAdapter.reopen
createRecord: (store, type, record) ->
root = this.rootForType(type)
data = {}
data[root] = record.toJSON(record, includeId: true)
@ajax @buildURL(root), "POST",
data: data,
context: this,
success: (json) ->
@didCreateRecord(store, type, record, json)
error: (xhr) ->
if xhr.status == 422
data = Ember.$.parseJSON xhr.responseText
# we don't materialize the errors, so we cannot use them in a view :(
store.recordWasInvalid record, data.errors
# TODO naive, does not consider translating the fields
DS.Model.reopen
toJSONwithErrors: ->
jQuery.extend @toJSON(), errors: @get('errors')
# options: error, success (both functions)
observeSaveOnce: (options) ->
callback = ->
outcome = 'success'
if @get('isDirty')
return if @get('isValid') # not submitted yet
outcome = 'error'
(options[outcome] || Ember.K).call(this)
@removeObserver('isDirty', callback)
@removeObserver('isValid', callback)
@addObserver('isDirty', callback)
@addObserver('isValid', callback)
errors: null
errorMessages: (->
messages = ''
if @get('errors')?
for field, errors of @get('errors')
if errors
for error in errors
messages += "#{field} #{error} "
messages
).property('isValid', 'errors', 'data')
hasErrors: (->
@get('errorMessages').match /\w+/
).property('errorMessages')
App.Router = Ember.Router.extend
enableLogging: true
location: 'hash'
openModal: (opts...) ->
# The used View must mixin App.ModalMixin
@get('applicationController').connectOutlet 'modal', opts...
closeModal: ->
@get('applicationController').disconnectOutlet 'modal'
root: Ember.Route.extend
index: Ember.Route.extend
route: '/'
connectOutlets: -> # nuffin
openMilestones: Ember.Router.transitionTo('milestones')
newMilestone: Ember.Router.transitionTo 'milestones.new'
milestones: Ember.Route.extend
route: '/milestones'
connectOutlets: (router) ->
router.get('applicationController').connectOutlet 'milestones', App.Milestone.find()
new: Ember.Route.extend
route: '/new'
connectOutlets: (router) ->
transaction = App.store.transaction()
milestone = transaction.createRecord App.Milestone
router.set 'currentTransaction', transaction
router.openModal 'newMilestone', milestone
save: (router) ->
if milestone = router.get('newMilestoneController.content')
transaction = router.get('currentTransaction')
milestone.observeSaveOnce
success: -> router.transitionTo('milestones')
error: ->
newTransaction = App.store.transaction()
newMilestone = newTransaction.createRecord App.Milestone, milestone.toJSON()
newMilestone.set 'errors', milestone.get('errors') # set by custom hook
router.set 'currentTransaction', newTransaction
router.set 'newMilestoneController.content', newMilestone
transaction.rollback()
transaction.commit()
cancel: Ember.Route.transitionTo('milestones')
exit: (router) -> router.closeModal()
{{#if hasErrors}}
<div>{{errorMessages}}</div>
{{/if}}
{{view Ember.TextField valueBinding="name" placeholder="Name"}}
<button {{action delete}}>Delete</button>
<button {{action save}}>Save</button>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment