Fork me on GitHub

We talk about new ideas, improvements and anything related to the increasing splendor of our garden.

If you would like to keep in touch, you can subscribe to the blog feed or to the mailing list. You can also follow zena_ on twitter for finer grains of zena development and news.


  1. 1.2.8 Release

    After nearly one year in the making, I am happy to announce the release of Zena 1.2.8.

    Changes related to users


    You can now mark some users as “is profile”. Once this is done, you can change all groups and auth settings for a user at once by assigning it the profile. Once this is done, you ensure that all users with a given profile have the same access rights.

    Define user as being a “profile”.

    is profile

    Set access rights through a profile.

    set profile

    Edit users through node[auth]

    Every user has an assigned node in the CMS. Until now, you could not use a single form to edit a user, including access rights and changing passwords. This can now be done through the pseudo attribute auth:

    <p do='input' name='first_name'/>
    <p do='input' name='last_name'/>
    <p do='select' label='Authorization' name='auth[profile]' type='profile' tprefix='false'/>

    There is a new user status called manager. This status is like a normal user but has the right to change user settings through the “auth” proxy (not the admin interface).

    We give the manager status to our clients so that they can create and change users but do not have access to the more complex (and error prone) admin interface.

    Changes related to queries

    Support for node scopes

    We can now search for content inside any node, not just projects or sections. Internally, Zena stores a fullpath field which contains the sequence of parental like this “1/24/35”.

    Example to search for all documents inside the current node (any depth):

    documents in sub_nodes

    This query is slower than the “project” or “section” scopes since it uses SQL “LIKE” on a text field. The index on this text field is limited to 255 characters (InnoDB limit) so it can get slow once the fullpath field exceeds this length.

    Support for home scope

    The “home” node is the root of an alias site. Since we had the “in site” scope to search anywhere inside the site’s data, we now have “in home” to only search for elements inside the site alias’ home node. This uses the “sub_nodes” scope internally.

    List all images inside the current alias site:

    documents in home

    Fulltext (sphinx) search from sqliss

    We can now use the fulltext search engine from inside sqliss. Simply use match>

    pages where fulltext match "#{params[:s]}" in site

    Search engines:

    • fulltext: searches inside the idx_text_high field (all published versioned)
    • sphinx: searches inside idx_text_high, idx_text_medium and idx_text_low with different priorities. This egine uses Spinx search (sphinx brick).

    Misc changes


    Grid now uses contenteditable by default. This should avoid some problems with cell display. There are also a number of new settings:

    • contenteditable (true). Enable html 5 contenteditable. Use ‘false’ to revert to old behavior.
    • pasteTable (true). Allow pasting multi cell content (from XLS for example).

    ajax rendering

    We now use a trick to avoid rendering an older Ajax query after a new one has been made. This is very visible when a live search is updating while the user types: the answer for “app” arrives after the result for “apple” and the end result is wrong.

    This uses a special parameter params[:zs] that increments each time an Ajax query is made and ensures query results are rendered sequentially for each dom id. The function checking for this is Zena.stampOk in zena.js. You need to update zena.js after installing the new gem:

    $ rake zena:assets
    ## exists: /home/.../public/javascripts/zena.js
       (a= overwrite all in same destination, s= overwrite none in same destination)
       overwrite (ayNs) ? y

    From the History log:

    Major changes

    • Added “update_attributes_without_clone” and “save_without_clone” to update attributes without versioning (used to sync attributes with external app). This does not change timestamps nor version or node author.
    • Added login retry wait delay on failures.
    • Added support for ‘in home’ or ‘from home’ in sqliss.
    • Added support for any scope with ‘in sub_nodes’ (uses fullpath field).
    • Added forced skin setting for sites (overwrites all but ACL skin settings).
    • Added support for profile users.
    • Added support to edit auth_user (linked user) through node’s pseudo attribute ‘auth’ (admin only) or through ‘auth_user’.
    • Created ‘manager’ user status: can edit users (only through ‘auth’).
    • Created ‘currency’ brick to fetch exchange rates.
    • Do not render an older Ajax reply if a newer one has already been received. This fixes the live query confusion.
    • Fixed loose requirements of httparty and rubyzip preventing installation.
    • Added ‘fsize’ method in view to display human readable octet size.
    • Added ‘pasteTable’ option to disable (by setting false) multi cell paste in grid.
    • Using contenteditable in grid instead of input form. Turn off with contenteditable option.
    • Fixed grid bug when leaving page while in input field.
    • Implemented ‘fulltext match’ or ‘sphinx match’ to use fulltext search in node query.
    • Added ‘group_id_to_name’ to visitor (enables group name display from node auth settings).
    • Added ‘site_readonly’ attribute to site with ‘site.readonly?’ rubyless. Useful for locking site during updates or migrations.

    Minor changes

    • Fixed gemspec to not include TextMate helper and selenium plugin.
    • Fixed xml generated file caching.
    • Added NODE_ID in ‘redir’ param on node creation (replaced by actual id).
    • Added fetch_html rubyless method to views (does an HTTP GET and returns body).
    • Fixed a bug in site alias zafu compilation (wrong home reference).
    • Fixed anchor in zazen url "":22#foo should link to anchor #foo in node ‘22’.
    • Prop eval returning nil for title properly replaces title with class name.
    • Type ctrl+return to enter a newline inside a grid.
    • Checking for defined accessors on links before passing to relation proxy (this enables custom code
      on relation modification without passing through RelationProxy).
    • Refuses to delete user node unless visitor is a manager.
    • Marks user as deleted and remove profile if node is destroyed.
    • Enable ‘parent_id’, ‘project_id’, ‘section_id’ Rubyless methods.
    • Fixed a critical bug related to changing a virtual class’ superclass.
    • Do not trigger Ajax ‘loading’ unless it takes more then 300 ms (avoid blink).
    • Fixed tlabel on [input] when there is a default label (should overwride default).
    • Fixed <p do='input'... so that the “p” tag is preserved.
    • Using shift+enter to insert carriage return in grid.
    • Improved jobs display.
    • Fixed a cache handling bug where a page could exist in the filesystem without having a cache entry
      in the database.
    • Fixed building templates on the fly with fs_skin on JS call.
    • Fixed rebuild_fullpath and added link in sites on web interface.
    • Removing bricks.yml and other initial config files from ‘rake zena:assets’.
    • Request for ‘login’ page while logged in redirects to home page instead of clearing session (fixes iPhone login issues).

    Gaspard Bucher

  2. Fix logrotate and vhost

    With Zena 1.2.5 and the support for media streaming (file must be public in order for seek to work), the vhost files need to be fixed.

    We are also experiencing a strange bug with logrotation and Passenger (“broken pipe” error). The logrotate fix prevents the logfile from being removed and should solve this issue.

    Here is a script that can help with this task if you are hosting many websites:

    #!/usr/bin/env ruby
    FIX_LOGROTATE = true
    FIX_VHOST     = true
    SITES         = {
      # host name         # languages list (first = default)
      # ''    => 'fr,en,de',
      system("cap apache2_setup")
      SITES.each do |s, lang_list|
        system("cap logrotate -s host='#{s}'")
    if FIX_VHOST
      SITES.each do |s, lang_list|
        system("cap create_vhost -s host='#{s}' -s lang_list='#{lang_list}'")

    Gaspard Bucher

  3. Added support for site alias

    In order to be able to use the same data for different websites, we have just added support for site alias. With this change we also introduced the possibility to change the root node (the node seen on the home page and referred to as “root” in queries).

    The “node without a parent” is now called the “orphan node” to differentiate it from the root node.

    site alias admin

    To add an alias, use rake:

    $ rake zena:mkalias HOST='' ALIAS=''

    To create all the vhost files, awstats settings, logrotate and such, use capistrano:

    $ mkalias -s host='' -s alias='' -s pass='stats_password'

    To create conditional execution in zafu, we can use “host” and “master_host” methods on the visitor’s site:

    <html do='set' host='' main_host='""'>
      <r:if test='host == main_host'>
        <link rel='Stylesheet' href='/main.css'/>
          <link rel='Stylesheet' href='/main.css'/>

    Gaspard Bucher

  4. 1.2.4 Release

    This is a bugfix release with some incremental improvements of the framework.


    • Added class filtering to Acl in ‘create’ action.
    • Uploading html files does not transform them into zafu (use .zafu ext for this)
    • Added file upload support from zena remote API.
    • Added ‘content_type’ regexp to force virtual class on Document creation.
    • Extended “create group” in vclass to be “allow group” (edit).
    • Added support for [versions_list] to display list of versions and status.


    • Support for raw html in zazen with or <html> tag. Must be allowed with notextile=’true’ on [zazen] tag.
    • Fixed preview of content with ACL (considering the POST on /zafu preview as a ‘read’).
    • Added support for “class not like Image” or “class <> Image” to sqliss.
    • Added url to clear cache with /sites/clear_cache (admin only).
    • encode_params now supports arrays.
    • Added ‘id’ to date input.
    • Fixed 404 error (would return some strange xml).
    • Toggle takes dynamic parameters for “js” and “arity”.
    • Fixed multiple toggles side-by-side.
    • Fixed nested blocks and class scoping.
    • Added support for “onUpdate” in [input] with date type.
    • Hash ‘keys’ returns sorted elements in zafu.
    • Improved computation of width and height in [img] when using ‘forced’ iformat.
    • Image width and height properties auto-fix themselves (need to read file on each display if not fixed).
    • Added support for ‘mode’ in encode_params.
    • Fixed [link_name]_status, _comment and other relation proxy methods.

    Gaspard Bucher

  5. Using dropbox to upload content

    With the upcoming 1.2.4 release, it will be easy to upload content by moving it to a shared dropbox/ftp/wuala folder.

    Setup folder sync on server

    For dropbox, this implies creating an account for the server and installing the dropbox daemon (dropbox on linux).

    Watch folder

    Once you have some kind of published folder (either with a sync service or with sftp or ssh or whatever), you need to install “inotify-tools” so that you can get notifications when files are added to the special folder.

    $ apt-get install inotify-tools

    Create an upload user

    Create a new user with access to the “remote” API and write access to an upload folder. In my case, I created a class named “Uploader” and create a single object of this class for uploads.

    Observing daemon script

    Below is an example of a “push to zena” script. Once you change the settings, you can simply start it with:

    $ ./push_to_zena start


    Run the script “undeamonized” to see logging information:

    $ ./push_to_zena run

    push_to_zena script

    The full source is here as this gives some idea on how to use the remote API.

    #!/usr/bin/env ruby
    require 'rubygems'
    require 'daemons'
    gem 'zena', '1.2.4'
    require 'zena/remote'
    # ================================== Configuration
    # Set to host
    HOST  = ''
    # Set to upload user's single access token
    # The token is visible in the admin form for the user.
    # Fullpath to watched folder.
    FOLDER = '/home/dropbox/Dropbox/MY_APP'
    # Set to upload node in site.
    UPLOADER_ID = 75
    # ================================== Configuration end
    app = Zena::Remote.connect(HOST, TOKEN)
    unless app.root
      puts "Could not connect to remote '\#{HOST}'..."
    # You could replace this with the id of an upload node:
    lib = app.first(UPLOADER_ID)
    if not lib
      puts "Could not find upload node #{UPLOADER_ID}."
    # This system call will block until there are changes to the upload folder
    wait_for_more = "inotifywait #{FOLDER.inspect}"
    Daemons.run_proc('push_to_zena') do
      loop do
        # Check for new files in
        Dir.foreach(FOLDER) do |path|
          next if path =~ /^\./
          path = "#{FOLDER}/#{path}"
          name = path[%r{([^/]+)$},1]
            # ignore sub-directories
          node = app['Node'].new(:title => name, :file =>, :parent => lib)
            puts "[NEW] #{} (#{node.title})"
            puts "[ERROR] #{node.errors}"
          File.delete path
        # wait for changed file in FOLDER
        if !system(wait_for_more)

    Gaspard Bucher

  6. older posts
    archives: 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014