Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Model.Coerce #19

Open
wants to merge 7 commits into from

2 participants

@ebaxt

Hi, I had a stab at adding the Model.Coerce that came up in the date serialization discussion. I've added integer, boolean, float and isoDate coercion as a start since that is what I needed, but one can easily add more by adding to Model.Coerce.

The api looks like this:

var Post = Model("post", function() {
  this.persistence(Model.REST, "/posts"),
  this.coerce(Model.Coerce,
  {
    category_id: "integer",
    lat: "float",
    published: "boolean",
    pubDate: "isoDate"
  })
})

If this is something you would like to use I'll add docs and stuff.

@benpickles
Owner

Cool, I'd like to include this in the main repo but probably not as core functionality. Rather, like this potential addition, I'd prefer it to be included manually with some sort of Plugin Architecture™ (probably very simple). What do you think?

@ebaxt

Sure, that sounds like a good idea. Are you currently working on something like that? If not I could probably give it a shot if you helped me define the API.

@benpickles
Owner
@benpickles
Owner

Looks like @github's new reply-via-email thing needs a little more work...

@benpickles
Owner

I've just pushed a few changes which include a simple "plugin architecture" which is used like so:

// first argument is the class
function SomeModuleOfFunctionality(klass, a, b) {
  // add some functionality to the class
  // can also add to the klass.prototype
}

// probably applied when declaring the model
var Project = Model("project", function() {
  this.use(SomeModuleOfFunctionality, "plus", "arguments")
}

// but can be called whenever
Project.use(SomethingElse)
@ebaxt

Cool. I'll update my pull request to use the new "plugin architecture".

@ebaxt

Moved Model.Coerce to Plugin.Coerce to make use of the new "plugin architecture". Not sure how you want to bundle the plugins so I just left it for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 29, 2011
  1. @ebaxt

    Missing json dep in Gemfile

    ebaxt authored
  2. @ebaxt

    Missing pygmentize in Gemfile

    ebaxt authored
Commits on Mar 31, 2011
  1. @ebaxt
Commits on Apr 1, 2011
  1. @ebaxt

    Fix isoDate bug in IE

    ebaxt authored
Commits on Apr 12, 2011
  1. @ebaxt
  2. @ebaxt
  3. @ebaxt

    Merged with upstream

    ebaxt authored
This page is out of date. Refresh to see the latest.
View
2  Gemfile
@@ -6,3 +6,5 @@ gem 'highlight', :require => 'simplabs/highlight'
gem 'nokogiri'
gem 'rdiscount'
gem 'sinatra'
+gem 'json'
+gem 'pygmentize'
View
4 Gemfile.lock
@@ -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)
@@ -22,6 +24,8 @@ DEPENDENCIES
closure-compiler
fewer (~> 0.2.0)
highlight
+ json
nokogiri
+ pygmentize
rdiscount
sinatra
View
1  lib/bundler.rb
@@ -41,6 +41,7 @@ def files
model_utils
model_version
model_base
+ plugin_coerce
)
end
View
2  src/model.js
@@ -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("-")
View
10 src/model_instance_methods.js
@@ -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.
@@ -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") {
View
91 src/plugin_coerce.js
@@ -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
+
+})();
+
+
View
1  test/tests/model_rest_test.js
@@ -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")
View
139 test/tests/plugin_coerce_test.js
@@ -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)")
+
+});
View
1  test/views/index.erb
@@ -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>
Something went wrong with that request. Please try again.