Changes

Jump to: navigation, search

Support/Notifications

6,991 bytes removed, 18:34, 25 January 2011
no edit summary
If a user registers, we should look for any anonymous watches with that email address and add them to the user, when the user is confirmed.
= Evolution of table structure Schedule =A strawman schedule—when to target having things up for review:* 1/24: fire() offthreading (up for review)* 1/26: object_id moving; de-duplication* 1/28: templating; confirmation views* 2/1: claiming; porting old client code & deleting old subsystem code (which go together)* 2/4: migrating old watches
== Iteration 1: Event type determines the format of the path column(s). == path path2* type** email/whatever 15 DocumentEvent whatever@whatever.com 15.en-US DocumentEvent 15.de.2837 DocumentEvent 15.en-US.30 DocumentSignificanceEvent TagEvent * optional. Choose the split per event type based on where orthogonality happens. ** An event type, which can span, frex, use cases 1-3 below. Determines the use of the spec fields.  == Iteration 2: Get ref-integ on content type. == content_type object_id path event_type email/? (usecase) 15 en-US DocumentLocaleEvent 2 15 DefaultEvent 1 15 2837 DefaultEvent 3 15 en-US.30 DocumentLocaleSignifEvent 7 15 de.L.A UntranslatedEvent 5 Don't make path col unicode: save space.  == Iteration 3: More normalization, yielding better queryability: orthogonality of path elements (not just hierarchy) is That should be possible without ridiculously inefficient substring queries. I don't think the joins would be too bad, but benches would be nice. Maintains ref integ of content type and object. == watches: id content_type object_id event_type user/email/whatever 1 15 2345 UntranslatedEvent  watch_elements: watch_id name value (int for size/speed/ad hoc joins) 1 lang CRC32(de) # Postgres doesn't have a CRC32 function, so ad hoc joins on string keys would be a pain. 1 localizable 1 1 approved 1  on UntranslatedEvent for object_id=2345 and content_type=15: select distinct email from watches left join watch_elements lang on watches.id=lang.watch_id and name='LANG' and (value IS NULL or value='de') left join watch_elements localizable on watches.id=localizable.watch_id and name='LOCA' and (value IS NULL or value='1') left join watch_elements approved on watches.id=approved.watch_id and name='APPR' and (value IS NULL or value='1') where content_type=15 and (object_id=2345 or object_id IS NULL) and event_type=UntranslatedEvent # It's an (n+1)-table join where n is the max length of the path (excluding content type and object ID). n of the joins, however, would be selecting among the same n rows: not a big deal.  == Iteration 4: Move content_type and object_id into watch_elements? See what the benches say. == watches: id event_type user/email/whatever 1 UntranslatedEvent watch_elements: watch_id name value (int for size/speed/ad hoc joins) 1 lang CRC32(de) # Postgres doesn't support CRC32 in the DB, so ad hoc joins on string keys would be a drag. 1 localizable 1 1 approved 1 1 content_type 15 1 object_id 2345  This will bring it up to an (n+1)-table join, n being the max length of a certain event type's path *including* content type and object ID. : Don't hamper work now because of Postgres. Assume we're on MySQL until Oracle shows up with a bill. = Features =* Maybe blocking an addy someday till a mail is confirmed (to prevent spamming)?* Daily digests, supported by 2 tables: digests: user event_type digest_elements (values plugged into the template specified by event_type): name value id 8634 hat_color red = API Sketch = class ObjectModifiedWatchType(object): """A notification which fires people on the modification of a specific object""" code = 'modi' # Key for the event_type column. Should be same size as a 32-bit int (but more memorable) if it; we store as a binary char(4). Migrations will be easy in case of collisions. All-lowercase codes are reserved for the notification app itself. @classmethod def fire(cls, obj): """Signal listener which sends any appropriate notifications""" @classmethod def create(cls, object): """Create (and save, and return) a watch which fires on the modification of the given object.""" ... objectModifiedSignal.register(ObjectCreatedEvent.listener) # modulo spelling class ContentTypeModifiedWatchType(...) code = 'modi' # Can share a key with the above since the NULL in the object_id column and the non-NULL in the content_type_id column are sufficient to distinguish them. class ContentTypeCreatedWatchType(...) code = 'crea' class LocaleCreationWatchType(...): # abstract because it doesn't provide a template. Might not really be worth making this abstract. We don't have a lot of locale-having things atm. """A notification which fires when an object (like a wiki Document) is created with a given locale. Works with anything that has a `locale` attr. """ code = 'WLCr' # convention: capital letters start a new word @classmethod def create(cls, locale, content_type=None): """Watch type for the creation of any object of a given locale. Specify a content_type to limit to a particular type. """ watch = Watches.create(content_type=content_type, object_id=None, event_type=cls.code, user=[figure out how to represent this relationship to support anonymous also]) # WatchElements implements a key/value store. We might use 4-char codes as the keys, again because they're more memorable than ints between 3 and because they have to be unique only within an event_type. Thus, none are reserved. WatchElements.create(watch=watch, name='locl', value=crc32(locale)) @classmethod def fire(cls, obj): # TODO: Can probably use the ORM for this: watches = execute("""select distinct user (or whatever) from watches inner join watch_elements locale on watches.id=lang.watch_id and name='locl' and value={obj.locale} where content_type IS NULL or content_type={obj.content_type} AND event_type={cls.code}""") for w in watches: cls.notify(obj) # builds and sends one mail, for example @classmethod def notify(cls, obj): # Send a mail or what have you. raise NotImplementError class WikiLocaleCreationWatchType(LocaleCreationWatchType): @classmethod def notify(cls, obj): send_mail_in_a_task(some_template.render(obj.this, obj.that, ...)) objectCreatedSignal.register(WikiLocaleCreationWatch.fire)  === Couple notes from James === * Wrapping text is not a bad thing. I had to go into the editor to read that without constant horizontal scrolling. :P* Why do you care about the size of the type? Disk space is a lot cheaper than the time to come up with and remember 4-byte sequences. Assume disk space is free and make it nice for humans.* <code>objectCreatedSignal.register()</code> [sic] gets at least two different @classmethods of different types. What does it... do?* In general, maybe you could break up this example into the parts provided by the notification system and then examples, of using it, and add more descriptions. It's not terribly clear at the moment.Have fun!
Confirm
574
edits

Navigation menu