jashkenas/coffee-script — pull request #3289 by tgriesser (2013-12-19 14:35:43+00:00)
Title: Extended hook after class body execution

After some discussion with @jashkenas, this pull request adds a feature similar to the long dead "extended" hook (#710, #841, #1960), but it places the hook at the end of the executable class body, with some (hopefully) reasonable real-world use cases:

There's a new library I'm writing which aims to sit atop [Bookshelf](http://bookshelfjs.org) and mimic Rails' ActiveRecord as closely as possible. I haven't quite hammered out all of the syntax/implementation yet, but let's take this as an example:

```coffee
class User extends ActiveRecord

tableName: 'users'

scopes:
moderators: -> @where('account_type', 'moderator')
active: -> @where('activated', true)
pending: -> @where('activated', false)

relations:
accounts: -> @hasMany(Account)
posts: -> @hasMany(Post)
site: -> @belongsTo(Site)

callbacks:
creating: ['checkEmail', 'checkUsername']

validators:
presence: ['name', 'login', 'email']
#...
```

In this case, the `extended` hook would be used to go through specific namespaces in the extending class (in this example, "scopes" and "relations"), and attach/wrap the methods as both static and prototype methods, so they can be chained like so:

```coffee
User
.active()
.moderators()
.take(5)
.then (moderatingUsers) ->
console.log(JSON.stringify(moderatingUsers))
.catch ActiveRecord.NotFound, (err) ->
res.json({error: err.message})
.catch (err) ->
console.log(err.stack)

User.posts().then(...
User.active().posts().then(...
```

A neat side-effect of doing things this way, is that `super` should work within the `relations.someMethod` or `scopes.someMethod` if extended further, as these methods would be attached directly to the parent class during `__extended`. Any naming collisions would be dealt with by the library.

The next example deals also with the Bookshelf.js project. The current `Bookshelf` object is a constructor, maintaining the underlying database connection information, exposing a `Model` and `Collection` from which objects can inherit:

```coffee
PG = new Bookshelf {...postgres connection info...}

class User extends PG.Model
tableName: 'users'

class Users extends PG.Collection
model: User
```

This works fine, except that by tying the models to a specific database, you lose the ability to have a base class used by multiple databases. I'm looking to move to the following:

```coffee
Postgres = Bookshelf.database {...postgres connection info...}
Sqlite = Bookshelf.database { ...sqlite connection info... }

class User extends Bookshelf.Model
tableName: 'users'
accounts: ->
@hasMany(Account)

class PGUser extends User
db: Postgres

class SqliteUser extends User
db: Sqlite
```

By late-binding the database to the class, it's possible for both the `PGUser` and `SqliteUser` to be an `instanceof User`.

Any methods specific to the data store being attached could be contained in the `db` object and late-bound to the extended class's prototype(s) during the `extended` hook, replacing base methods which are stubbed out as a rejected promises in the base Model; creating something of a pseduo-abstract class in JS.

As @jashkenas pointed out in the other ticket, there can be arguments made for adding an `extended` hook both before and after the class body execution. I believe the use cases for having an `extended` before the class body are less compelling - if you'd like a class method to exist, you'd typically define it on the parent class - and if you needed to get rid of it during extension, you could do so in this new `extended` hook. The only cases where it'd be necessary to have the hook "before" would be to add a method/property just to be able to immediately reference it, which seems a bit inefficient (why not just define it on the extended class).

Having an `extended` hook after the class body is executed is hopefully a more convincing case which could help build some good looking class interfaces.

(Note: First time playing around with the CS source, so if there's a better way to do what I set out for in this PR, I'm all ears)