Return-Path: Delivered-To: apmail-couchdb-user-archive@www.apache.org Received: (qmail 13324 invoked from network); 14 Mar 2011 19:55:11 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.3) by minotaur.apache.org with SMTP; 14 Mar 2011 19:55:11 -0000 Received: (qmail 30240 invoked by uid 500); 14 Mar 2011 19:55:09 -0000 Delivered-To: apmail-couchdb-user-archive@couchdb.apache.org Received: (qmail 30199 invoked by uid 500); 14 Mar 2011 19:55:09 -0000 Mailing-List: contact user-help@couchdb.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: user@couchdb.apache.org Delivered-To: mailing list user@couchdb.apache.org Received: (qmail 30191 invoked by uid 99); 14 Mar 2011 19:55:09 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 14 Mar 2011 19:55:09 +0000 X-ASF-Spam-Status: No, hits=0.3 required=5.0 tests=RCVD_IN_DNSWL_LOW,SPF_SOFTFAIL X-Spam-Check-By: apache.org Received-SPF: softfail (athena.apache.org: transitioning domain of simeon@simeons.net does not designate 209.85.215.180 as permitted sender) Received: from [209.85.215.180] (HELO mail-ey0-f180.google.com) (209.85.215.180) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 14 Mar 2011 19:55:05 +0000 Received: by eyg24 with SMTP id 24so1964540eyg.11 for ; Mon, 14 Mar 2011 12:54:42 -0700 (PDT) MIME-Version: 1.0 Received: by 10.14.16.208 with SMTP id h56mr6209161eeh.19.1300132482597; Mon, 14 Mar 2011 12:54:42 -0700 (PDT) Received: by 10.14.29.11 with HTTP; Mon, 14 Mar 2011 12:54:42 -0700 (PDT) In-Reply-To: <4D79A5C2.3000006@gmail.com> References: <4D79A5C2.3000006@gmail.com> Date: Mon, 14 Mar 2011 12:54:42 -0700 Message-ID: Subject: Re: validate_doc_update responsibilities From: "Simeon F. Willbanks" To: user@couchdb.apache.org Cc: Patrick Barnes Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable Patrick, This sounds like a cleaner approach, since you aren't "extending" the validate_doc_update responsibilities. Plus, you don't need to send an intentionally bad request to the database. Thanks, Simeon On Thu, Mar 10, 2011 at 8:32 PM, Patrick Barnes wrote: > I suggest using couchapp to store your field meta-info (validation, forma= t, > etc) in a config/ folder or file, then you can: > > a) Include it into your validate_doc_update.js file like: > function (newDoc, oldDoc, userCtx) { > // !json config.form > > b) Refer to it from elsewhere like: > (PHP, because that is what I'm using) > $config =3D $gateway->get('_design/couchapp')->config->form; > > So if you have a bunch of required fields, validation_doc_update can proc= ess > them like: > for(field in config.form.fields) { > =C2=A0 =C2=A0... > =C2=A0 =C2=A0if (field.required =3D=3D true && !(field.name in newDoc)) > =C2=A0 =C2=A0 =C2=A0 =C2=A0throw({forbidden:"Missing required field"}); > =C2=A0 =C2=A0... > > And your application can use them like: > foreach($config->form->fields as $f) { > =C2=A0 =C2=A0... > =C2=A0 =C2=A0$e =3D $form->createElement($f->type, $f->name) > =C2=A0 =C2=A0 =C2=A0 =C2=A0... > =C2=A0 =C2=A0 =C2=A0 =C2=A0->setRequired($f->required); > } > > Obviously there's plenty of application-specific design work, but this so= rt > of pattern allows you to store lots of validation info in a way that you = can > store it in the db (where it belongs), and still access it within the > application. > > Works for me, > -Patrick > > > On 10/03/2011 11:00 AM, Simeon F. Willbanks wrote: >> >> Justin, >> >> Thank you for the reply and helping me brainstorm a solution. I like >> the !code macro idea, but my CouchDB server isn't on the same machine >> as my application. They communicate through a network. Could I do >> this? >> >> function(newDoc, oldDoc, userCtx) { >> =C2=A0//!code path/to/validate.py >> =C2=A0validate(newDoc, oldDoc, userCtx); >> } >> >> path/to/validate.py would be network aware and would return valid >> JavaScript. >> >> Thanks, >> Simeon >> >> On Wed, Mar 9, 2011 at 1:27 PM, Justin Walgran >> =C2=A0wrote: >>> >>> Simeon, >>> >>> the !code macro in CouchApp was created so you can DRY up your >>> functions in the exact way you want. If you extract your validator, >>> you can include it in both your validate_doc_update function and your >>> client code. Something like this: >>> >>> function(newDoc, oldDoc, userCtx) { >>> =C2=A0//!code path/to/validate.js >>> =C2=A0validate(newDoc, oldDoc, userCtx); >>> } >>> >>> // Content of validate.js >>> var validate =3D function(newDoc, oldDoc, userCtx) { >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0var Errors =3D {count: 0}; >>> >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0var Message =3D function(field, type, text) = { >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0this.type =3D ty= pe || 'string'; >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0this.text =3D te= xt || field + ' is required'; >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0this.required = =3D true; >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0}; >>> >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0var require =3D function(field, type, text) = { >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0if (!newDoc[fiel= d]) { >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0Errors[field] =3D new Message(field, type, text); >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0Errors.count +=3D 1; >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0}; >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0}; >>> >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0if (newDoc.type =3D=3D 'post') { >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0require('title')= ; >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0require('created= _at', 'datetime'); >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0require('body'); >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0require('author'= ); >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0}; >>> >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0if (newDoc.type =3D=3D 'comment') { >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0require('name'); >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0require('created= _at', 'datetime'); >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0require('comment= ', 'string', 'You may not leave an empty >>> comment'); >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0}; >>> >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0if (Errors.count> =C2=A00) { >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0throw({forbidden= : JSON.stringify(Errors)}); >>> =C2=A0 =C2=A0 =C2=A0 =C2=A0}; >>> } >>> >>> If you refactor a little further, you could go the next step and >>> generate a form from the configuration of the validator without the >>> making the extra, intentionally bad request to the server. >>> >>> Justin >>> >>> >>> On Wed, Mar 9, 2011 at 4:10 PM, Simeon F. Willbanks >>> =C2=A0wrote: >>>> >>>> =C2=A0From the 'CouchDB The Definitive Guide', "CouchDB uses the >>>> validate_doc_update function to prevent invalid or unauthorized >>>> document updates from proceeding." This clearly defines the >>>> validate_doc_update function's responsibility. That said, can we >>>> extend this responsibility a bit? I'd rather not redefine valid >>>> document attributes in my external application since >>>> validate_doc_update is already doing the work (DRY). Maybe the >>>> application can query the validate_doc_update function for the exact >>>> attributes of a valid document. Here is a proof of concept >>>> validate_doc_update function. >>>> >>>> function(newDoc, oldDoc, userCtx) { >>>> =C2=A0Errors =3D {count: 0}; >>>> >>>> =C2=A0function Message(field, type, text) { >>>> =C2=A0 =C2=A0this.type =3D type || 'string'; >>>> =C2=A0 =C2=A0this.text =3D text || field + ' is required'; >>>> =C2=A0 =C2=A0this.required =3D true; >>>> =C2=A0}; >>>> >>>> =C2=A0function require(field, type, text) { >>>> =C2=A0 =C2=A0if (!newDoc[field]) { >>>> =C2=A0 =C2=A0 =C2=A0Errors[field] =3D new Message(field, type, text); >>>> =C2=A0 =C2=A0 =C2=A0Errors.count +=3D 1; >>>> =C2=A0 =C2=A0}; >>>> =C2=A0}; >>>> >>>> =C2=A0if (newDoc.type =3D=3D 'post') { >>>> =C2=A0 =C2=A0require('title'); >>>> =C2=A0 =C2=A0require('created_at', 'datetime'); >>>> =C2=A0 =C2=A0require('body'); >>>> =C2=A0 =C2=A0require('author'); >>>> =C2=A0}; >>>> >>>> =C2=A0if (newDoc.type =3D=3D 'comment') { >>>> =C2=A0 =C2=A0require('name'); >>>> =C2=A0 =C2=A0require('created_at', 'datetime'); >>>> =C2=A0 =C2=A0require('comment', 'string', 'You may not leave an empty = comment'); >>>> =C2=A0}; >>>> >>>> =C2=A0if (Errors.count> =C2=A00) { >>>> =C2=A0 =C2=A0throw({forbidden: JSON.stringify(Errors)}); >>>> =C2=A0}; >>>> } >>>> >>>> In my application, before putting a new document or building a >>>> document input UI (web form), I can "query" the validate_doc_update >>>> function like so: >>>> >>>> $ curl -X PUT couchdb:5984/basic/a1a0d5f1e202b48e5bc55f616d0021ff -d >>>> '{"type":"post"}' >>>> >>>> {"error":"forbidden","reason":"{\"count\":4,\"title\":{\"type\":\"stri= ng\",\"text\":\"title >>>> is >>>> required\",\"required\":true},\"created_at\":{\"type\":\"datetime\",\"= text\":\"created_at >>>> is >>>> required\",\"required\":true},\"body\":{\"type\":\"string\",\"text\":\= "body >>>> is >>>> required\",\"required\":true},\"author\":{\"type\":\"string\",\"text\"= :\"author >>>> is required\",\"required\":true}}"} >>>> >>>> =C2=A0From the response, I can decode the JSON object's reason propert= y to a >>>> useful array. >>>> >>>> array >>>> =C2=A0'count' =3D> =C2=A0int 4 >>>> =C2=A0'title' =3D> >>>> =C2=A0 =C2=A0array >>>> =C2=A0 =C2=A0 =C2=A0'type' =3D> =C2=A0string 'string' (length=3D6) >>>> =C2=A0 =C2=A0 =C2=A0'text' =3D> =C2=A0string 'title is required' (leng= th=3D17) >>>> =C2=A0 =C2=A0 =C2=A0'required' =3D> =C2=A0boolean true >>>> =C2=A0'created_at' =3D> >>>> =C2=A0 =C2=A0array >>>> =C2=A0 =C2=A0 =C2=A0'type' =3D> =C2=A0string 'datetime' (length=3D8) >>>> =C2=A0 =C2=A0 =C2=A0'text' =3D> =C2=A0string 'created_at is required' = (length=3D22) >>>> =C2=A0 =C2=A0 =C2=A0'required' =3D> =C2=A0boolean true >>>> =C2=A0'body' =3D> >>>> =C2=A0 =C2=A0array >>>> =C2=A0 =C2=A0 =C2=A0'type' =3D> =C2=A0string 'string' (length=3D6) >>>> =C2=A0 =C2=A0 =C2=A0'text' =3D> =C2=A0string 'body is required' (lengt= h=3D16) >>>> =C2=A0 =C2=A0 =C2=A0'required' =3D> =C2=A0boolean true >>>> =C2=A0'author' =3D> >>>> =C2=A0 =C2=A0array >>>> =C2=A0 =C2=A0 =C2=A0'type' =3D> =C2=A0string 'string' (length=3D6) >>>> =C2=A0 =C2=A0 =C2=A0'text' =3D> =C2=A0string 'author is required' (len= gth=3D18) >>>> =C2=A0 =C2=A0 =C2=A0'required' =3D> =C2=A0boolean true >>>> >>>> =C2=A0From this array, I can build the input UI or validate the new do= cument >>>> before putting. >>>> >>>> To reiterate, I am trying to DRY up the validation >>>> rules/responsibilities by defining them in one spot. This proof of >>>> concept uses the validate_doc_update function in CouchDB. The >>>> validate_doc_update function is now responsible for preventing invalid >>>> documents from being PUT to CouchDB, and it can respond to application >>>> queries for what constitutes a valid document. >>>> >>>> So, is this a bad idea? Is there a more idiomatic way to accomplish th= is >>>> goal? >>>> >>>> Thanks, >>>> Simeon >>>> >>> >> >