[Sputnik-list] Version
Yuri Takhteyev
yuri at sims.berkeley.edu
Mon Mar 17 20:41:54 GMT+2 2008
> Is there any reason the inflator moves the version id from
> node.version to node._version.id? Is this something I can count on?
> The current interaction between the different parts of versium
> admittedly throws me off a bit.
Yes, this is to allow you to have your own "version" field inside the node.
At the core versium level (soon to be the only "versium" level), a
revision of a node has six fields: "version", "id", "data", "author",
"comment", "timestamp". The value of "data" is assumed to be some
kind of string. Core versium doesn't ask what's in it. But opaque
strings aren't very useful, so we have inflators. An inflator takes
the content of the "data" field and turns it into a Lua table somehow.
(How this is done depends on the inflator, but the one I use just
runs it as Lua code and returns the environment). This gives us two
tables: one representing the "raw" revision of the node, and another
one representing the data field inflated into a table. We mostly want
to use the latter, but we don't want to lose track fo the former. So,
we want to somehow attach it to the inflated table. The way I do this
is by creating a metatable with three fields "_version" (set to all
revision-specific information), "_raw" (representing the original
value of "data"), and "_id" (representing the value of the "id"
field). I prefix them with underscores so that they are less likely
to conflict with the client's fields.
So, if the "raw" node looks like:
id = "The_Coolest_Painting"
version = "000001"
author = "Jim"
comment = "fixed a typo"
data = [[
title = "Doodle #1"
author = "yuri"
version = "second attempt"
tags = "cool, talented"
proto = "@Painting"
]]
then it might get inflated into:
title = "Doodle #1"
author = "yuri"
version = "second attempt"
tags = "cool, talented"
proto = "@Painting"
with the following in it's metatable:
_version = {
id = "000001",
author = "Jim",
comment = "Fixed a typo"
}
_raw = [[
title = "Doodle #1"
author = "yuri"
version = "second attempt"
tags = "cool, talented"
proto = "@Painting"
]]
_id = "The_Coolest_Painting"
The idea here is that inflated_node.version is client's field. It can
mean whatever the client wants it to mean. In my example above, I
assume that the client decides to use "version" field to store the
information about which version of the artwork this record describes,
which has nothing to do with the version of the record. But if the
client of the inflated node wants to see the original, _versium's_
version info, they can still get it: inflated_node._version.id.
Similarly inflated_node.author specifies the author of the artwork
(Yuri, the author of "Doodle #1"), while the original versium's
"author" field specified the user who updated the record last ("Jim",
the author of revision "000001" of "The_Coolest_Painting"). Note that
I leave it to the client to decide whether they want to use
confusingly similar names.
If you use "versium.smart" (to be renamed "saci"), this continues
further. What we want to do at this point is pull in (inherit) a
whole bunch of fields from "@Painting" and also to expand "tags" into
a list. As we inherit the fields, we want to make sure that we can
later pull them apart and know what's ours and what was inherited. We
do this with the "SmartNode:wrap" method. First we make a new table
for the node, put the original (but already inflated) node into
"_vnode" field of that table, and make it that table's metatable. Now
we can start safely adding all sorts of fields (e.g. node.repository)
without worrying that they might get saved. We then look for fields
defined by the prototype and set them too. (Again, we can always go
back and see our local field by examining node._vnode.) Next we want
to "activate" some fields, turning them from strings into something
else. E.g., we might want to turn the value of "tags" from "cool,
talented" to {"cool", "talented"}. Again, we don't want to lose our
original values. So, again, we make a new table, put the "old" node
(already with inheritance) into ._inactive, then start "activating"
fields. At the end of the day we end up with something like this (in
pseudo-code):
actions = {
order_print = function() ...., -- inherited form @Painting, activated
}
tags = {"cool", "talented"} -- activated
_inactive = {
actions = [[ -- inherited from @Painting, not yet activated
order_print = "paintings.order_print"
]]
_vnode = {
title = "Doodle #1"
author = "yuri"
version = "second attempt"
tags = "cool, talented"
proto = "@Painting"
METATABLE :
_version = {
id = "000001",
author = "Jim",
comment = "Fixed a typo"
}
_raw = [[
title = "Doodle #1"
author = "yuri"
version = "second attempt"
tags = "cool, talented"
proto = "@Painting"
]]
_id = "The_Coolest_Painting"
}
METATABLE = _vnode
METATABLE = _inactive
Note that node.actions is a table of functions, node._inactive.actions
is a string (which gets inflated into a table), and
node._vnode.actions is not defined, because there isn't actually a
field called "actions" defined locally. On the other hand, node.title
just gives us the string "Doodle #1", just as node._inactive.title or
node._vnode.title would.
The assumption here is that the client would mostly want to deal with
things like node.actions.show() or node.templates.RSS and we want to
make this easy. This comes at the expense of making saving a little
trickier we'll have to reach into node._vnode to figure out what to
update there, and we'll then need to rebuild the whole tree from the
updated _vnode values. But the good news is that SmartNode:update()
does this for you. So, if we want to add a new tag to our node, for
instance, we would do something like this:
new_tags = node._vnode.tags..", clever"
node = node:update({tags=new_tags}, {"tags"})
Note that the first parameter supplies the values, the second the list
of fields to be updated. This allows us to not worry if the first
table has values that we don't want updated. I.e., we could write:
node = node:update({tags=new_tags, foo="bar"}, {"tags"})
but the new value of "foo" won't be applied. Practically, this allows
me to pass to node:update() all unhashed POST parameters, without
filtering them in Sputnik. node:update() gets called in sputnik.lua:
function Sputnik:update_node_with_params(node, params)
node:update(params, node.fields)
self:prime_node(node)
return node
end
which is called from sputnik/actions/wiki.lua:
function actions.save(node, request, sputnik)
...
local new_node = sputnik:update_node_with_params(node, request.params)
new_node = sputnik:activate_node(new_node)
new_node:save(request.user, request.params.summary,
{minor=request.params.minor})
return new_node.actions.show(new_node, request, sputnik)
...
end
In other words, we take the node and the POST parameters (unhashed),
and send that to Sputnik:update_node_with_params(). That in turn
sends the node and params to node:update(), but tells it to only
update those fields that are listed in node.fields. Then we call
Sputnik:prime_node() to add some Sputnik-specific bells and whistles
before returning it. (Looking at this code now, I am realizing that
Sputnik:activate_node() gotta be a bit confusing, since it mostly
unrelated to SmartNode:activate(). I'll think of a better than for
"activate_node".) Note that at this point saving the node is easy: we
basically deflate node._vnode and send it to versium. The flow of
control here is as follows:
new_node:save(author, summary, extra)
calls self.repository:save_node(self, author, comment, extra)
calls self.versium:save_version(node._id,
self.versium:deflate(node._vnode), author, comment, extra)
I hope this helps.
- yuri
--
http://sputnik.freewisdom.org/
More information about the Sputnik-list
mailing list