From ded62a2108fb3ae28c747624561a621b949e01a5 Mon Sep 17 00:00:00 2001 From: William Cunningham Date: Thu, 23 Jul 2015 20:23:41 -0400 Subject: [PATCH 1/5] create payment method with customer --- lib/fake_braintree/customer.rb | 15 +++++++++++++++ spec/fake_braintree/customer_spec.rb | 26 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/lib/fake_braintree/customer.rb b/lib/fake_braintree/customer.rb index 8d46c55..59ca9b7 100644 --- a/lib/fake_braintree/customer.rb +++ b/lib/fake_braintree/customer.rb @@ -20,6 +20,10 @@ def create if invalid? response_for_invalid_card else + load_payment_method!( + @customer_hash.delete('payment_method_nonce'), + @customer_hash['credit_card'] ||= {} + ) credit_cards = customer_hash['credit_cards'] create_customer_with(customer_hash) credit_cards.each { |card| add_credit_card_to_registry(card) } @@ -118,6 +122,11 @@ def set_default_credit_card(credit_card_hash) def generate_credit_cards_from(new_credit_card_hash) if new_credit_card_hash.present? && new_credit_card_hash.is_a?(Hash) + load_payment_method!( + new_credit_card_hash.delete('payment_method_nonce'), + new_credit_card_hash + ) + new_credit_card_hash['bin'] = new_credit_card_hash['number'][0..5] new_credit_card_hash['last_4'] = new_credit_card_hash['number'][-4..-1] new_credit_card_hash['token'] = credit_card_token(new_credit_card_hash) @@ -199,5 +208,11 @@ def set_customer_id def credit_card_token(credit_card_hash_without_token) md5("#{credit_card_hash_without_token['number']}#{@customer_hash['merchant_id']}") end + + def load_payment_method!(nonce, credit_card_hash) + return unless nonce + payment_method_hash = FakeBraintree.registry.payment_methods[nonce] + credit_card_hash.merge!(payment_method_hash) + end end end diff --git a/spec/fake_braintree/customer_spec.rb b/spec/fake_braintree/customer_spec.rb index ca27b76..8a208f1 100644 --- a/spec/fake_braintree/customer_spec.rb +++ b/spec/fake_braintree/customer_spec.rb @@ -54,6 +54,32 @@ expect(result).to be_success end + it 'creates a credit card from payment method nonce in credit card hash' do + nonce = FakeBraintree::PaymentMethod.tokenize_card({ + number: TEST_CC_NUMBER, + expiration_date: '04/2016' + }) + result = Braintree::Customer.create( + credit_card: { payment_method_nonce: nonce } + ) + + credit_cards = Braintree::Customer.find(result.customer.id).credit_cards + expect(credit_cards.size).to eq 1 + expect(credit_cards.first.expiration_date).to eq '04/2016' + end + + it 'creates a credit card from payment method nonce' do + nonce = FakeBraintree::PaymentMethod.tokenize_card({ + number: TEST_CC_NUMBER, + expiration_date: '04/2016' + }) + result = Braintree::Customer.create('payment_method_nonce' => nonce) + + credit_cards = Braintree::Customer.find(result.customer.id).credit_cards + expect(credit_cards.size).to eq 1 + expect(credit_cards.first.expiration_date).to eq '04/2016' + end + it 'does not overwrite a passed customer id' do result = Braintree::Customer.create({ 'id' => '123' }) From b457cf9258470b7e0372bb33ac6cd6a92c6de224 Mon Sep 17 00:00:00 2001 From: William Cunningham Date: Sun, 26 Jul 2015 19:00:42 -0400 Subject: [PATCH 2/5] add support for `Braintree::PaymentMethod.delete` --- README.md | 1 + lib/fake_braintree/sinatra_app.rb | 8 ++++++++ spec/fake_braintree/payment_method_spec.rb | 20 ++++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/README.md b/README.md index 8f00fb9..a2a2b6e 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ of them (yet). * `Braintree::PaymentMethod.create` * `Braintree::PaymentMethod.find` * `Braintree::PaymentMethod.update` +* `Braintree::PaymentMethod.delete` ### Subscription * `Braintree::Subscription.cancel` diff --git a/lib/fake_braintree/sinatra_app.rb b/lib/fake_braintree/sinatra_app.rb index e002c0f..40e879d 100644 --- a/lib/fake_braintree/sinatra_app.rb +++ b/lib/fake_braintree/sinatra_app.rb @@ -185,6 +185,14 @@ def hash_from_request_body_with_key(key) CreditCard.new(updates, options).update end + # Braintree::PaymentMethod.delete + delete '/merchants/:merchant_id/payment_methods/any/:credit_card_token' do + cc_hash = {} + options = {token: params[:credit_card_token], merchant_id: params[:merchant_id]} + + CreditCard.new(cc_hash, options).delete + end + # Braintree::CreditCard.delete delete '/merchants/:merchant_id/payment_methods/credit_card/:credit_card_token' do cc_hash = {} diff --git a/spec/fake_braintree/payment_method_spec.rb b/spec/fake_braintree/payment_method_spec.rb index e67dd7d..0eeee4b 100644 --- a/spec/fake_braintree/payment_method_spec.rb +++ b/spec/fake_braintree/payment_method_spec.rb @@ -34,6 +34,26 @@ def token_for(month, year) end end +describe 'Braintree::PaymentMethod.delete' do + it 'successfully deletes a payment method' do + token = cc_token # creates card + + result = Braintree::PaymentMethod.delete(token) + + expect(result).to eq true + + expect { + Braintree::PaymentMethod.find(token) + }.to raise_error(Braintree::NotFoundError) + end + + it 'raises an error for a nonexistent payment method' do + expect { + Braintree::PaymentMethod.delete('foo') + }.to raise_error(Braintree::NotFoundError) + end +end + describe 'Braintree::PaymentMethod.create' do it 'allows creating a credit card without a customer' do nonce = FakeBraintree::PaymentMethod.tokenize_card(build_credit_card_hash) From b693b935f6eef09775a871dc67089de8449677bc Mon Sep 17 00:00:00 2001 From: matt knox Date: Tue, 28 Jul 2015 12:15:40 -0700 Subject: [PATCH 3/5] Fix crash on empty payment_method Not totally sure how we got to this state, but we got many crashes where FakeBraintree.registry.payment_methods[nonce] was nil and therefore couldn't merge. This is the dumbest fix possible, but it works. --- lib/fake_braintree/sinatra_app.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/fake_braintree/sinatra_app.rb b/lib/fake_braintree/sinatra_app.rb index 40e879d..e0bbef7 100644 --- a/lib/fake_braintree/sinatra_app.rb +++ b/lib/fake_braintree/sinatra_app.rb @@ -213,7 +213,8 @@ def hash_from_request_body_with_key(key) else payment_method_hash = hash_from_request_body_with_key('payment_method') nonce = payment_method_hash.delete('payment_method_nonce') - FakeBraintree.registry.payment_methods[nonce].merge(payment_method_hash) + h = FakeBraintree.registry.payment_methods[nonce] || {} + FakeBraintree.registry.payment_methods[nonce] = h.merge(payment_method_hash) end options = {merchant_id: params[:merchant_id]} From 73c4f76cab5dd5b78bb90517e9d6083f35b716d7 Mon Sep 17 00:00:00 2001 From: matt knox Date: Thu, 30 Jul 2015 13:05:07 -0700 Subject: [PATCH 4/5] shorter line to placate hound --- lib/fake_braintree/sinatra_app.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/fake_braintree/sinatra_app.rb b/lib/fake_braintree/sinatra_app.rb index e0bbef7..615c814 100644 --- a/lib/fake_braintree/sinatra_app.rb +++ b/lib/fake_braintree/sinatra_app.rb @@ -213,8 +213,8 @@ def hash_from_request_body_with_key(key) else payment_method_hash = hash_from_request_body_with_key('payment_method') nonce = payment_method_hash.delete('payment_method_nonce') - h = FakeBraintree.registry.payment_methods[nonce] || {} - FakeBraintree.registry.payment_methods[nonce] = h.merge(payment_method_hash) + h = FakeBraintree.registry.payment_methods + h[nonce] = (h[nonce] || {}).merge(payment_method_hash) end options = {merchant_id: params[:merchant_id]} From 17cbb17da9ff03e455ffb5e86f36c5c3e888284b Mon Sep 17 00:00:00 2001 From: William Cunningham Date: Mon, 24 Aug 2015 14:30:24 -0400 Subject: [PATCH 5/5] update client to 2.11.2 and drop-in to 1.8.3 --- asset_versions.yml | 4 +- .../1.7.0/braintree-dropin-internal.min.js | 12 ---- .../1.8.3/braintree-dropin-internal.min.js | 12 ++++ .../{1.7.0 => 1.8.3}/braintree-dropin.css | 56 +++++++++--------- .../images/2x-sf9a66b4f5a.png | Bin .../dropin/{1.7.0 => 1.8.3}/inline-frame.html | 12 ++-- .../dropin/{1.7.0 => 1.8.3}/modal-frame.html | 12 ++-- .../{1.7.0 => 1.8.3}/vendor/jquery-2.1.0.js | 0 .../{1.7.0 => 1.8.3}/vendor/modernizr.js | 0 .../{1.7.0 => 1.8.3}/vendor/normalize.css | 0 spec/dummy/public/braintree.js | 10 ++-- 11 files changed, 59 insertions(+), 59 deletions(-) delete mode 100644 lib/fake_braintree/braintree_assets/dropin/1.7.0/braintree-dropin-internal.min.js create mode 100644 lib/fake_braintree/braintree_assets/dropin/1.8.3/braintree-dropin-internal.min.js rename lib/fake_braintree/braintree_assets/dropin/{1.7.0 => 1.8.3}/braintree-dropin.css (98%) rename lib/fake_braintree/braintree_assets/dropin/{1.7.0 => 1.8.3}/images/2x-sf9a66b4f5a.png (100%) rename lib/fake_braintree/braintree_assets/dropin/{1.7.0 => 1.8.3}/inline-frame.html (85%) rename lib/fake_braintree/braintree_assets/dropin/{1.7.0 => 1.8.3}/modal-frame.html (62%) rename lib/fake_braintree/braintree_assets/dropin/{1.7.0 => 1.8.3}/vendor/jquery-2.1.0.js (100%) rename lib/fake_braintree/braintree_assets/dropin/{1.7.0 => 1.8.3}/vendor/modernizr.js (100%) rename lib/fake_braintree/braintree_assets/dropin/{1.7.0 => 1.8.3}/vendor/normalize.css (100%) diff --git a/asset_versions.yml b/asset_versions.yml index 627ed70..a61957b 100644 --- a/asset_versions.yml +++ b/asset_versions.yml @@ -1,3 +1,3 @@ --- -client_version: 2.9.0 -dropin_version: 1.7.0 +client_version: 2.11.2 +dropin_version: 1.8.3 diff --git a/lib/fake_braintree/braintree_assets/dropin/1.7.0/braintree-dropin-internal.min.js b/lib/fake_braintree/braintree_assets/dropin/1.7.0/braintree-dropin-internal.min.js deleted file mode 100644 index 5c4d5e2..0000000 --- a/lib/fake_braintree/braintree_assets/dropin/1.7.0/braintree-dropin-internal.min.js +++ /dev/null @@ -1,12 +0,0 @@ -!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;"undefined"!=typeof window?e=window:"undefined"!=typeof global?e=global:"undefined"!=typeof self&&(e=self),(e.braintree||(e.braintree={})).dropin=t()}}(function(){var define,module,exports;return function t(e,n,r){function i(a,s){if(!n[a]){if(!e[a]){var u="function"==typeof require&&require;if(!s&&u)return u(a,!0);if(o)return o(a,!0);var l=new Error("Cannot find module '"+a+"'");throw l.code="MODULE_NOT_FOUND",l}var c=n[a]={exports:{}};e[a][0].call(c.exports,function(t){var n=e[a][1][t];return i(n?n:t)},c,c.exports,t,e,n,r)}return n[a].exports}for(var o="function"==typeof require&&require,a=0;au;u++)if(t=s[u],a=this._events[t]){if(this._events[t]=i=[],e||r)for(p=0,h=a.length;h>p;p++)o=a[p],(e&&e!==o.callback&&e!==o.callback._callback||r&&r!==o.context)&&i.push(o);i.length||delete this._events[t]}return this},trigger:function(t){if(!this._events)return this;var e=a.call(arguments,1);if(!l(this,"trigger",t,e))return this;var n=this._events[t],r=this._events.all;return n&&c(n,e),r&&c(r,arguments),this},stopListening:function(t,e,r){var i=this._listeningTo;if(!i)return this;var o=!e&&!r;r||"object"!=typeof e||(r=this),t&&((i={})[t._listenId]=t);for(var a in i)t=i[a],t.off(e,r,this),(o||n.isEmpty(t._events))&&delete this._listeningTo[a];return this}},u=/\s+/,l=function(t,e,n,r){if(!n)return!0;if("object"==typeof n){for(var i in n)t[e].apply(t,[i,n[i]].concat(r));return!1}if(u.test(n)){for(var o=n.split(u),a=0,s=o.length;s>a;a++)t[e].apply(t,[o[a]].concat(r));return!1}return!0},c=function(t,e){var n,r=-1,i=t.length,o=e[0],a=e[1],s=e[2];switch(e.length){case 0:for(;++rh;h++)this.trigger("change:"+s[h],this,p[s[h]],r)}if(l)return this;if(!u)for(;this._pending;)r=this._pending,this._pending=!1,this.trigger("change",this,r);return this._pending=!1,this._changing=!1,this},unset:function(t,e){return this.set(t,void 0,n.extend({},e,{unset:!0}))},clear:function(t){var e={};for(var r in this.attributes)e[r]=void 0;return this.set(e,n.extend({},t,{unset:!0}))},hasChanged:function(t){return null==t?!n.isEmpty(this.changed):n.has(this.changed,t)},changedAttributes:function(t){if(!t)return this.hasChanged()?n.clone(this.changed):!1;var e,r=!1,i=this._changing?this._previousAttributes:this.attributes;for(var o in t)n.isEqual(i[o],e=t[o])||((r||(r={}))[o]=e);return r},previous:function(t){return null!=t&&this._previousAttributes?this._previousAttributes[t]:null},previousAttributes:function(){return n.clone(this._previousAttributes)},fetch:function(t){t=t?n.clone(t):{},void 0===t.parse&&(t.parse=!0);var e=this,r=t.success;return t.success=function(n){return e.set(e.parse(n,t),t)?(r&&r(e,n,t),void e.trigger("sync",e,n,t)):!1},R(this,t),this.sync("read",this,t)},save:function(t,e,r){var i,o,a,s=this.attributes;if(null==t||"object"==typeof t?(i=t,r=e):(i={})[t]=e,r=n.extend({validate:!0},r),i&&!r.wait){if(!this.set(i,r))return!1}else if(!this._validate(i,r))return!1;i&&r.wait&&(this.attributes=n.extend({},s,i)),void 0===r.parse&&(r.parse=!0);var u=this,l=r.success;return r.success=function(t){u.attributes=s;var e=u.parse(t,r);return r.wait&&(e=n.extend(i||{},e)),n.isObject(e)&&!u.set(e,r)?!1:(l&&l(u,t,r),void u.trigger("sync",u,t,r))},R(this,r),o=this.isNew()?"create":r.patch?"patch":"update","patch"===o&&(r.attrs=i),a=this.sync(o,this,r),i&&r.wait&&(this.attributes=s),a},destroy:function(t){t=t?n.clone(t):{};var e=this,r=t.success,i=function(){e.trigger("destroy",e,e.collection,t)};if(t.success=function(n){(t.wait||e.isNew())&&i(),r&&r(e,n,t),e.isNew()||e.trigger("sync",e,n,t)},this.isNew())return t.success(),!1;R(this,t);var o=this.sync("delete",this,t);return t.wait||i(),o},url:function(){var t=n.result(this,"urlRoot")||n.result(this.collection,"url")||F();return this.isNew()?t:t.replace(/([^\/])$/,"$1/")+encodeURIComponent(this.id)},parse:function(t,e){return t},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return!this.has(this.idAttribute)},isValid:function(t){return this._validate({},n.extend(t||{},{validate:!0}))},_validate:function(t,e){if(!e.validate||!this.validate)return!0;t=n.extend({},this.attributes,t);var r=this.validationError=this.validate(t,e)||null;return r?(this.trigger("invalid",this,r,n.extend(e,{validationError:r})),!1):!0}});var d=["keys","values","pairs","invert","pick","omit"];n.each(d,function(t){h.prototype[t]=function(){var e=a.call(arguments);return e.unshift(this.attributes),n[t].apply(n,e)}});var f=e.Collection=function(t,e){e||(e={}),e.model&&(this.model=e.model),void 0!==e.comparator&&(this.comparator=e.comparator),this._reset(),this.initialize.apply(this,arguments),t&&this.reset(t,n.extend({silent:!0},e))},m={add:!0,remove:!0,merge:!0},v={add:!0,remove:!1};n.extend(f.prototype,s,{model:h,initialize:function(){},toJSON:function(t){return this.map(function(e){return e.toJSON(t)})},sync:function(){return e.sync.apply(this,arguments)},add:function(t,e){return this.set(t,n.extend({merge:!1},e,v))},remove:function(t,e){var r=!n.isArray(t);t=r?[t]:n.clone(t),e||(e={});var i,o,a,s;for(i=0,o=t.length;o>i;i++)s=t[i]=this.get(t[i]),s&&(delete this._byId[s.id],delete this._byId[s.cid],a=this.indexOf(s),this.models.splice(a,1),this.length--,e.silent||(e.index=a,s.trigger("remove",s,this,e)),this._removeReference(s,e));return r?t[0]:t},set:function(t,e){e=n.defaults({},e,m),e.parse&&(t=this.parse(t,e));var r=!n.isArray(t);t=r?t?[t]:[]:n.clone(t);var i,o,a,s,u,l,c,p=e.at,d=this.model,f=this.comparator&&null==p&&e.sort!==!1,v=n.isString(this.comparator)?this.comparator:null,y=[],g=[],b={},w=e.add,_=e.merge,x=e.remove,C=!f&&w&&x?[]:!1;for(i=0,o=t.length;o>i;i++){if(u=t[i]||{},a=u instanceof h?s=u:u[d.prototype.idAttribute||"id"],l=this.get(a))x&&(b[l.cid]=!0),_&&(u=u===s?s.attributes:u,e.parse&&(u=l.parse(u,e)),l.set(u,e),f&&!c&&l.hasChanged(v)&&(c=!0)),t[i]=l;else if(w){if(s=t[i]=this._prepareModel(u,e),!s)continue;y.push(s),this._addReference(s,e)}s=l||s,!C||!s.isNew()&&b[s.id]||C.push(s),b[s.id]=!0}if(x){for(i=0,o=this.length;o>i;++i)b[(s=this.models[i]).cid]||g.push(s);g.length&&this.remove(g,e)}if(y.length||C&&C.length)if(f&&(c=!0),this.length+=y.length,null!=p)for(i=0,o=y.length;o>i;i++)this.models.splice(p+i,0,y[i]);else{C&&(this.models.length=0);var E=C||y;for(i=0,o=E.length;o>i;i++)this.models.push(E[i])}if(c&&this.sort({silent:!0}),!e.silent){for(i=0,o=y.length;o>i;i++)(s=y[i]).trigger("add",s,this,e);(c||C&&C.length)&&this.trigger("sort",this,e)}return r?t[0]:t},reset:function(t,e){e||(e={});for(var r=0,i=this.models.length;i>r;r++)this._removeReference(this.models[r],e);return e.previousModels=this.models,this._reset(),t=this.add(t,n.extend({silent:!0},e)),e.silent||this.trigger("reset",this,e),t},push:function(t,e){return this.add(t,n.extend({at:this.length},e))},pop:function(t){var e=this.at(this.length-1);return this.remove(e,t),e},unshift:function(t,e){return this.add(t,n.extend({at:0},e))},shift:function(t){var e=this.at(0);return this.remove(e,t),e},slice:function(){return a.apply(this.models,arguments)},get:function(t){return null==t?void 0:this._byId[t]||this._byId[t.id]||this._byId[t.cid]},at:function(t){return this.models[t]},where:function(t,e){return n.isEmpty(t)?e?void 0:[]:this[e?"find":"filter"](function(e){for(var n in t)if(t[n]!==e.get(n))return!1;return!0})},findWhere:function(t){return this.where(t,!0)},sort:function(t){if(!this.comparator)throw new Error("Cannot sort a set without a comparator");return t||(t={}),n.isString(this.comparator)||1===this.comparator.length?this.models=this.sortBy(this.comparator,this):this.models.sort(n.bind(this.comparator,this)),t.silent||this.trigger("sort",this,t),this},pluck:function(t){return n.invoke(this.models,"get",t)},fetch:function(t){t=t?n.clone(t):{},void 0===t.parse&&(t.parse=!0);var e=t.success,r=this;return t.success=function(n){var i=t.reset?"reset":"set";r[i](n,t),e&&e(r,n,t),r.trigger("sync",r,n,t)},R(this,t),this.sync("read",this,t)},create:function(t,e){if(e=e?n.clone(e):{},!(t=this._prepareModel(t,e)))return!1;e.wait||this.add(t,e);var r=this,i=e.success;return e.success=function(t,n){e.wait&&r.add(t,e),i&&i(t,n,e)},t.save(null,e),t},parse:function(t,e){return t},clone:function(){return new this.constructor(this.models)},_reset:function(){this.length=0,this.models=[],this._byId={}},_prepareModel:function(t,e){if(t instanceof h)return t;e=e?n.clone(e):{},e.collection=this;var r=new this.model(t,e);return r.validationError?(this.trigger("invalid",this,r.validationError,e),!1):r},_addReference:function(t,e){this._byId[t.cid]=t,null!=t.id&&(this._byId[t.id]=t),t.collection||(t.collection=this),t.on("all",this._onModelEvent,this)},_removeReference:function(t,e){this===t.collection&&delete t.collection,t.off("all",this._onModelEvent,this)},_onModelEvent:function(t,e,n,r){("add"!==t&&"remove"!==t||n===this)&&("destroy"===t&&this.remove(e,r),e&&t==="change:"+e.idAttribute&&(delete this._byId[e.previous(e.idAttribute)],null!=e.id&&(this._byId[e.id]=e)),this.trigger.apply(this,arguments))}});var y=["forEach","each","map","collect","reduce","foldl","inject","reduceRight","foldr","find","detect","filter","select","reject","every","all","some","any","include","contains","invoke","max","min","toArray","size","first","head","take","initial","rest","tail","drop","last","without","difference","indexOf","shuffle","lastIndexOf","isEmpty","chain","sample"];n.each(y,function(t){f.prototype[t]=function(){var e=a.call(arguments);return e.unshift(this.models),n[t].apply(n,e)}});var g=["groupBy","countBy","sortBy","indexBy"];n.each(g,function(t){f.prototype[t]=function(e,r){var i=n.isFunction(e)?e:function(t){return t.get(e)};return n[t](this.models,i,r)}});var b=e.View=function(t){this.cid=n.uniqueId("view"),t||(t={}),n.extend(this,n.pick(t,_)),this._ensureElement(),this.initialize.apply(this,arguments),this.delegateEvents()},w=/^(\S+)\s*(.*)$/,_=["model","collection","el","id","attributes","className","tagName","events"];n.extend(b.prototype,s,{tagName:"div",$:function(t){return this.$el.find(t)},initialize:function(){},render:function(){return this},remove:function(){return this.$el.remove(),this.stopListening(),this},setElement:function(t,n){return this.$el&&this.undelegateEvents(),this.$el=t instanceof e.$?t:e.$(t),this.el=this.$el[0],n!==!1&&this.delegateEvents(),this},delegateEvents:function(t){if(!t&&!(t=n.result(this,"events")))return this;this.undelegateEvents();for(var e in t){var r=t[e];if(n.isFunction(r)||(r=this[t[e]]),r){var i=e.match(w),o=i[1],a=i[2];r=n.bind(r,this),o+=".delegateEvents"+this.cid,""===a?this.$el.on(o,r):this.$el.on(o,a,r)}}return this},undelegateEvents:function(){return this.$el.off(".delegateEvents"+this.cid),this},_ensureElement:function(){if(this.el)this.setElement(n.result(this,"el"),!1);else{var t=n.extend({},n.result(this,"attributes"));this.id&&(t.id=n.result(this,"id")),this.className&&(t["class"]=n.result(this,"className"));var r=e.$("<"+n.result(this,"tagName")+">").attr(t);this.setElement(r,!1)}}}),e.sync=function(t,r,i){var o=C[t];n.defaults(i||(i={}),{emulateHTTP:e.emulateHTTP,emulateJSON:e.emulateJSON});var a={type:o,dataType:"json"};if(i.url||(a.url=n.result(r,"url")||F()),null!=i.data||!r||"create"!==t&&"update"!==t&&"patch"!==t||(a.contentType="application/json",a.data=JSON.stringify(i.attrs||r.toJSON(i))),i.emulateJSON&&(a.contentType="application/x-www-form-urlencoded",a.data=a.data?{model:a.data}:{}),i.emulateHTTP&&("PUT"===o||"DELETE"===o||"PATCH"===o)){a.type="POST",i.emulateJSON&&(a.data._method=o);var s=i.beforeSend;i.beforeSend=function(t){return t.setRequestHeader("X-HTTP-Method-Override",o),s?s.apply(this,arguments):void 0}}"GET"===a.type||i.emulateJSON||(a.processData=!1),"PATCH"===a.type&&x&&(a.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")});var u=i.xhr=e.ajax(n.extend(a,i));return r.trigger("request",r,u,i),u};var x=!("undefined"==typeof window||!window.ActiveXObject||window.XMLHttpRequest&&(new XMLHttpRequest).dispatchEvent),C={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};e.ajax=function(){return e.$.ajax.apply(e.$,arguments)};var E=e.Router=function(t){t||(t={}),t.routes&&(this.routes=t.routes),this._bindRoutes(),this.initialize.apply(this,arguments)},k=/\((.*?)\)/g,P=/(\(\?)?:\w+/g,S=/\*\w+/g,A=/[\-{}\[\]+?.,\\\^$|#\s]/g;n.extend(E.prototype,s,{initialize:function(){},route:function(t,r,i){n.isRegExp(t)||(t=this._routeToRegExp(t)),n.isFunction(r)&&(i=r,r=""),i||(i=this[r]);var o=this;return e.history.route(t,function(n){var a=o._extractParameters(t,n);o.execute(i,a),o.trigger.apply(o,["route:"+r].concat(a)),o.trigger("route",r,a),e.history.trigger("route",o,r,a)}),this},execute:function(t,e){t&&t.apply(this,e)},navigate:function(t,n){return e.history.navigate(t,n),this},_bindRoutes:function(){if(this.routes){this.routes=n.result(this,"routes");for(var t,e=n.keys(this.routes);null!=(t=e.pop());)this.route(t,this.routes[t])}},_routeToRegExp:function(t){return t=t.replace(A,"\\$&").replace(k,"(?:$1)?").replace(P,function(t,e){return e?t:"([^/?]+)"}).replace(S,"([^?]*?)"),new RegExp("^"+t+"(?:\\?([\\s\\S]*))?$")},_extractParameters:function(t,e){var r=t.exec(e).slice(1);return n.map(r,function(t,e){return e===r.length-1?t||null:t?decodeURIComponent(t):null})}});var O=e.History=function(){this.handlers=[],n.bindAll(this,"checkUrl"),"undefined"!=typeof window&&(this.location=window.location,this.history=window.history)},T=/^[#\/]|\s+$/g,I=/^\/+|\/+$/g,M=/msie [\w.]+/,j=/\/$/,N=/#.*$/;O.started=!1,n.extend(O.prototype,s,{interval:50,atRoot:function(){return this.location.pathname.replace(/[^\/]$/,"$&/")===this.root},getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getFragment:function(t,e){if(null==t)if(this._hasPushState||!this._wantsHashChange||e){t=decodeURI(this.location.pathname+this.location.search);var n=this.root.replace(j,"");t.indexOf(n)||(t=t.slice(n.length))}else t=this.getHash();return t.replace(T,"")},start:function(t){if(O.started)throw new Error("Backbone.history has already been started");O.started=!0,this.options=n.extend({root:"/"},this.options,t),this.root=this.options.root,this._wantsHashChange=this.options.hashChange!==!1,this._wantsPushState=!!this.options.pushState,this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var r=this.getFragment(),i=document.documentMode,o=M.exec(navigator.userAgent.toLowerCase())&&(!i||7>=i);if(this.root=("/"+this.root+"/").replace(I,"/"),o&&this._wantsHashChange){var a=e.$('