Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ gem 'highlight', :require => 'simplabs/highlight'
gem 'nokogiri'
gem 'rdiscount'
gem 'sinatra'
gem 'json'
gem 'pygmentize'
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ GEM
rack
highlight (1.1.2)
activesupport (>= 2.0.0)
json (1.5.1)
nokogiri (1.4.4)
pygmentize (0.0.2)
rack (1.2.1)
rdiscount (1.6.8)
sinatra (1.1.3)
Expand All @@ -22,6 +24,8 @@ DEPENDENCIES
closure-compiler
fewer (~> 0.2.0)
highlight
json
nokogiri
pygmentize
rdiscount
sinatra
1 change: 1 addition & 0 deletions lib/bundler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def files
model_utils
model_version
model_base
plugin_coerce
)
end

Expand Down
2 changes: 1 addition & 1 deletion src/model.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
var Model = function(name, func) {
// The model constructor.
var model = function(attributes) {
this.attributes = Model.Utils.extend({}, attributes)
this.attributes = this.setAttributeValues(Model.Utils.extend({}, attributes))
this.changes = {};
this.errors = new Model.Errors(this);
this.uid = [name, Model.UID.generate()].join("-")
Expand Down
10 changes: 9 additions & 1 deletion src/model_instance_methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ Model.InstanceMethods = {
return this.attr()
},

setAttributeValue: function(name, value) {
return value
},

setAttributeValues: function(attributes) {
return attributes
},

attr: function(name, value) {
if (arguments.length === 0) {
// Combined attributes/changes object.
Expand All @@ -13,7 +21,7 @@ Model.InstanceMethods = {
// Clean up any stale changes.
delete this.changes[name];
} else {
this.changes[name] = value;
this.changes[name] = this.setAttributeValue(name, value)
}
return this;
} else if (typeof name === "object") {
Expand Down
91 changes: 91 additions & 0 deletions src/plugin_coerce.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
if (!(typeof Plugin === 'function')) {
Plugin = function() {
}
}

Plugin.Coerce = (function() {

var plugin = function (klass, aTypes) {
var attributeTypes = aTypes;

var augmentSetAttributeValue = function(orig_fn) {
return function() {
var name = arguments[0]
var value = arguments[1]
return coerceAttribute(name, orig_fn.call(this, name, value))
}
}

var augmentSetAttributeValues = function(orig_fn) {
return function() {
return coerceAttributes(orig_fn.apply(this, arguments))
}
}

function coerceAttribute(name, value) {
if (!(attributeTypes === undefined || attributeTypes[name] === undefined)) {
return plugin[attributeTypes[name]](value);
} else {
return value
}
}

function coerceAttributes(attributes) {
for (var name in attributes) {
attributes[name] = coerceAttribute(name, attributes[name])
}
return attributes
}

klass.prototype.setAttributeValue = augmentSetAttributeValue(klass.prototype.setAttributeValue)
klass.prototype.setAttributeValues = augmentSetAttributeValues(klass.prototype.setAttributeValues)
}

plugin.integer = function(value) {
return parseInt(value)
}

plugin['boolean'] = function(value) {
return (!(value === false || value === "false" ||
value === 0 || value === "0"));
}

plugin['float'] = function(value) {
return parseFloat(value)
}

plugin.isoDate = function(value) {
//http://zetafleet.com/blog/javascript-dateparse-for-iso-8601
/**
* Date.parse with progressive enhancement for ISO-8601, version 2
* © 2010 Colin Snover <http://zetafleet.com>
* Released under MIT license.
*/
(function () {
var origParse = Date.parse;
Date.parse = function (date) {
var timestamp = origParse(date), minutesOffset = 0, struct;
if (isNaN(timestamp) && (struct = /^(\d{4}|[+\-]\d{6})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3,}))?)?(?:(Z)|([+\-])(\d{2})(?::?(\d{2}))?))?/.exec(date))) {
if (struct[8] !== 'Z') {
minutesOffset = +struct[10] * 60 + (+struct[11]);

if (struct[9] === '+') {
minutesOffset = 0 - minutesOffset;
}
}

timestamp = Date.UTC(+struct[1], +struct[2] - 1, +struct[3], +struct[4], +struct[5] + minutesOffset, +struct[6], +struct[7].substr(0, 3));
}

return timestamp;
};
}());

return new Date(Date.parse(value))
}

return plugin

})();


1 change: 1 addition & 0 deletions test/tests/model_rest_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ test("create with named params in resource path", function() {
equal(request.url, "/root/3/nested/2/posts");
});


test("update with named params in resource path", function() {
var Post = Model("post", function() {
this.persistence(Model.REST, "/root/:root_id/nested/:nested_id/posts")
Expand Down
139 changes: 139 additions & 0 deletions test/tests/plugin_coerce_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
module("Plugin.Coerce")

test("Integer coercion", function() {
equals(Plugin.Coerce.integer("1"), 1)
equals(Plugin.Coerce.integer("1"), parseInt("1"))
equals(Plugin.Coerce.integer("4.5"), parseInt("4"))
equals(Plugin.Coerce.integer("4.5"), parseInt("4"))
same(Plugin.Coerce.integer("abc"), NaN)
});

test("Boolean coercion", function() {
equals(Plugin.Coerce.boolean("1"), true)
equals(Plugin.Coerce.boolean("0"), false)
equals(Plugin.Coerce.boolean("true"), true)
equals(Plugin.Coerce.boolean("false"), false)
equals(Plugin.Coerce.boolean(true), true)
equals(Plugin.Coerce.boolean(false), false)
});

test("Float coercion", function() {
equals(Plugin.Coerce.float("1.23"), 1.23)
equals(Plugin.Coerce.float("1"), 1.0)
same(Plugin.Coerce.float("abc"), NaN)
});

test("IsoDate coercion", function() {
equals(Plugin.Coerce.isoDate("2010-08-21T02:03:44Z"), "Sat Aug 21 2010 04:03:44 GMT+0200 (CEST)")
equals(Plugin.Coerce.isoDate("test"), "Invalid Date")
ok(Plugin.Coerce.isoDate("2011-12-31T00:00:00Z") instanceof Date)
equals(Plugin.Coerce.isoDate("2011-12-31T00:00:00Z"), "Sat Dec 31 2011 01:00:00 GMT+0100 (CET)")
});


test("attribute types with coercion", function() {
var Post = Model("post", function() {
this.use(Plugin.Coerce,
{
category_id: "integer",
lat: "float",
published: "boolean",
pubDate: "isoDate"
})
})

var post = new Post({
category_id: "1.4",
lat: "12345.6789",
published: "false",
pubDate: "2010-08-21T02:03:44Z"
})

equals(post.attr("category_id"), 1)
equals(post.attr("lat"), 12345.6789)
equals(post.attr("published"), false)
equals(post.attr("pubDate"), "Sat Aug 21 2010 04:03:44 GMT+0200 (CEST)")

post.attr("published", true)
equals(post.attr("published"), true)

post.attr("pubDate", "2011-12-31T00:00:00Z")
ok(post.attributes.pubDate instanceof Date, "Should be instance of date!")
equals(post.attr("pubDate"), "Sat Dec 31 2011 01:00:00 GMT+0100 (CET)")

post.attr({pubDate: "2010-01-01T00:00:0Z", category_id: 2, lat: 1.2, published: true})
equals(post.attr("category_id"), 2)
equals(post.attr("lat"), 1.2)
equals(post.attr("published"), true)
equals(post.attr("pubDate"), "Fri Jan 01 2010 01:00:00 GMT+0100 (CET)")
});


test("create types with coercion should stringify types correctly", function() {
var Post = Model("post", function() {
this.persistence(Model.REST, "/posts"),
this.use(Plugin.Coerce,
{
category_id: "integer",
lat: "float",
published: "boolean",
pubDate: "isoDate"
})
})

var post = new Post({
category_id: "1.4",
lat: "12345.6789",
published: "false",
pubDate: "2010-08-21T02:03:44Z"
})

AjaxSpy.start();
stop();

post.save(function(success) {
ok(success);
start();
});

equals(AjaxSpy.requests.length, 1, "one request should have been made");

var request = AjaxSpy.requests.shift();

equals(request.type, "POST");
equals(request.url, "/posts");
equals(request.data, "{\"post\":{\"category_id\":1,\"lat\":12345.6789,\"published\":false,\"pubDate\":\"2010-08-21T02:03:44.000Z\"}}")

});

test("Custom coercions", function() {
Plugin.Coerce.uppercase = function(value) {
return value.toUpperCase()
}

var Post = Model("post", function() {
this.use(Plugin.Coerce,
{
title: "uppercase",
category_id: "integer",
lat: "float",
published: "boolean",
pubDate: "isoDate"
})
})

var post = new Post({
title: "title",
category_id: "1.4",
lat: "12345.6789",
published: "false",
pubDate: "2010-08-21T02:03:44Z"
})

equals(post.attr("title"), "TITLE")
equals(post.attr("category_id"), 1)
equals(post.attr("lat"), 12345.6789)
equals(post.attr("published"), false)
equals(post.attr("pubDate"), "Sat Aug 21 2010 04:03:44 GMT+0200 (CEST)")

});
1 change: 1 addition & 0 deletions test/views/index.erb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<script src="tests/model_local_storage_test.js"></script>
<script src="tests/inheritance_test.js"></script>
<script src="tests/plugin_test.js"></script>
<script src="tests/plugin_coerce_test.js"></script>
</head>
<body>
<h1 id="qunit-header">js-model Tests</h1>
Expand Down