The two most common use case for patterns are:
As patterns are implemented (pattern cards created, hooks declared, etc.), they are registered in a big hash (internal or external to ruby process depending on needs) by their pattern key. To find the patterns for a given card, we generate a set of pattern keys for that card, and look up the patterns for each of those keys.
We will likely cache settings, as well as all cacheable views, on the card. When a pattern card is changes, all effected cards have their cache updated.
On 10/12/09 6:03 PM, Lewis Hoffman wrote:
On Sat, Oct 10, 2009 at 5:47 AM, Gerry Gleason gerryg@inbox.com wrote:
I like these broad categories. I'm a little fuzzy about Card storage being at the same level-- it seems like there's a lot of change/revision handling and content processing that we just wouldn't do if the card were actually stored on another wagn.So we would have these chunks to implement cards:
- Card content model (we haven't talked about this much, but I think this is where the hooks Lewis is talking about comes in. Task: catalog the content processing we do now: wiki-rendering to handle [[]] and stuff, slot based type editors, other type rendering
- Card naming model (the semantics of the virtual fields, name and key, + segmentation of names)
- Card storage model (Wagn is a flat namespace derived directly from the naming model) We have just scratched the surface in these discussions about how we might want to extend Wagn with respect to namespaces, multiple wagns refering to each others content. name resolution and scopes/contexts.
- Change model (i.e. how we handle revisions) -- I will come back to this along with Ethan's question about what I said earlier about revisions.
I agree, this is fuzzy. I'm going to address this in a separate post to keep this one shorter.
Gerry
module Pattern
class << self
@@store = {}
def create( spec )
matching_classes = active_classes.select {|pattern_class| pattern_class.recognize( spec ) }
raise("invalid pattern") if matching_classes.length < 1
raise("pattern conflict") if matching_classes.length > 1
matching_classes.first.new( spec )
end
def register( pattern )
@@store[ pattern.to_key ] ||= []
@@store[ pattern.to_key ] << pattern
end
def patterns_for(card)
card.pattern_keys ||= keys_for(card)
card.pattern_keys.map { |key| @@store[ key ] }.flatten.order_by(&:priority)
end
def cards_for(pattern)
Card.search( pattern.to_wql )
end
private
def keys_for(card)
active_classes.map { |pattern_class| pattern_class.key_for(card) }.compact
end
def active_classes
subclasses & System.setting('*active pattern classes')
end
end
class Base
def self.key_for(card)
raise("not implemented")
end
def self.recognize(wql)
raise("not implemented")
end
def intialize(spec)
@spec = spec
end
def to_key
raise("not implemented")
end
def to_wql
@spec
end
end
end
class TypePattern < Pattern::Base
def self.key_for(card)
"Type:#{card.type}"
end
def self.recognize(spec)
spec[:type] && spec[:type].is_a?(String) && spec.keys.length == 1
end
def to_key
"Type:#{@spec[:type]}"
end
end
class RightNamePattern < Pattern::Base
def self.key_for(card)
return nil unless card.junction?
"RightName:#{card.name.tag_name}"
end
def self.recognize(spec)
spec[:right] && spec[:right].is_a?(String) && spec.keys.length == 1
end
def to_key
"RightName:#{spec[:right]}"
end
end
class TypeRightNamePattern < Pattern::Base
def self.key_for(card)
return nil unless card.junction?
"TypeRightName:#{card.type}:#{card.name.tag_name}"
end
def self.recognize(spec)
spec[:right] && spec[:right].is_a?(String) &&
spec[:type] && spec[:type].is_a?(String) &&
spec.keys.length == 2
end
def to_key
"TypeRightName:#{spec[:type]}:#{spec[:right]}"
end
end
A Pattern Class is a certain kind of pattern. eg.
A Pattern Key is a string that is an intermediary index between patterns and cards. Within a pattern class, each key corresponds 1-1 with a pattern, and each card corresponds 1-1 with a key.
note: we need assure that no two pattern classes could generate the same key.
challenge: It would be (much) better not to have to know about pattern classes as a hook author or Pattern Card Wagneer. Thus we face the challenge of taking a pattern definition syntax (which could be a subset of WQL) and figuring out which pattern class it belongs to- or if none, to declare the pattern invalid.
pseudo code:
class TypePattern < PatternClass
def key_for(card)
"Type:#{card.type}"
end
end
on [:create,:update]
(re) generate pattern keys
look up applicable patterns
sort patterns by precedence
store cacheable settings
on :create, :type=>"Pattern"
add to pattern store by key
on :update, :type=>"Pattern"
remove and re-add to pattern store.
on :delete, :type=>"Pattern"
remove pattern from pattern store
on [:create, :update, :delete], :type=>"Pattern"
look up matched cards
foreach affected card
regenerate setting store
for now, settings are handled by pattern cards defined by Wagneers in the card data, while hooks and views are defined by coders and live in memory. However we'll soon want to define views and possibly hooks as well in card data. At that point we have to have a mechanism for managing precedence across "dynamic" card-defined patterns and "static" module defined patterns. This seems like a tricky issue.
As it stands, a pattern can be created either by creating a PatternCard as a Wagneer, or by defining a view or hook as a module author. The index of Pattern Card patterns of course have to live in an inter-process space, such as memcache.
The index of hook and view pattern most naturally lives inside the ruby memory space.
However, having to look in two places, and sorting precedence between them seems thorny.
One alternative would be have the view and hook definitions actually find_or_create() corresponding pattern cards at server startup time. This sounds like it could be a syncronization nightmare, but it would at least be DRY, the precedence would be clear, and it would potentially open an interesting window in the the internals from the wagn interface.
update: I've partially resolved this issue: module-defined hooks and views live in an internal store. card-defined hooks and views should always be cacheable, so they live in the card settings. this way on the (frequent) hook and view lookups we never need to access the external pattern store; we just look in card settings and the internal store. This doesn't fully address the precedence issue. It's less of an issue for hooks since they're additive anyway. For views, we could take the crude approach that any card-defined view overrides any module-defined view. We could start there and see if we really need to interleave precedence.
Here's the general process:
This process might be triggered by something like @card.setting(:form).
NOTE: while the two steps above are a good short representation of the process, it's possible we may want a hybrid approach. When we're looking for a given setting, we might only need the strongest pattern, in which case loading the entire pattern list could be overkill. In that case, we might decide to construct only as much of the pattern list at any given time as we need to find a setting. At first glance, that looks unlikely to be worth the effort, especially if a card gets lots of viewings between pattern-precedence-altering events.
A second, important, use case involves performing a WQL search and having it further limited by a certain pattern. The only instance of this we've considered so far is returning only cards that the user has permission to read, which means relying on the read permission setting (see Pattern Cards+permissions ). But we might imagine it coming up again for bulk updates, bulk deletes, etc. One route would be to cache the read permission as we have done, but this is complicated by the new permissions design in that a given card might have multiple parties. Another possibility is that we cache the id of the +*read setting card that governs each card, and for each user we cache ids for all the +*read cards that grant the user permission. Oooh. That's kind of cool. The trick there is that we'd have to be able to update +*read id caches when new settings emerged or pattern precedence shifted. But if we want (a) to be caching permissions and (b) to have a means of updating lots of permissions at once, this is probably a problem we're going to have to face in one form or another. (Needs further discussion).
I'm not sure I fully understand the patterns proposal, but I was trying to explore the code use cases. At first I was thinking that you would just go from a card, which matches a set of patterns, each of which can have a set of attribute values (Lewis suggested thes are the plus cards of the pattern, but I'm not sure if this is best.
What I imagine instead is the patterns connected with a card as a set of resources available to configure the operation of each hook defined by the application.
--- Gerry
I came to this by thinking about what the code that uses all this would look like and where it would be situated. Below is an attempt to psuedo code a card controller:
class CardController << ApplicationController
before_filter :action_ok
def action_ok
auth = action2auth(action)
patterns { |pat| pat.permissions(self,auth) }
end
end
# posting with just the permission part so far ...
--Gerry Gleason.....Sun Oct 11 14:09:09 -0700 2009