On Dec 20, 7:00 am, Garry Hill <[email protected]> wrote:
> Hi all,
>
> I'm developing a CMS system based on Sequel that uses the concept of
> 'revisions' to manage the publish workflow and need some advice about
> the best way to implement this in Sequel.
>
> Let me explain a bit more. The editable content of the CMS is held in
> a table called simply "content". When a user decides to make some
> changes live the system duplicates the "content" table into another
> table named after the next revision number, e.g. "revision_023". This
> "revision_023" table is then used by the Content model in place of
> "content" on the public site, thus sand-boxing the published content
> from the edited content.

I should prefix my advice by saying that creating non-temporary tables
at runtime is almost always a bad idea.  I can see how it seems like a
quick fix, but it will likely bite you later.  An alternative is a
simple "revision" column in the content table.  To create a new
version, instead of copying the table, you can just copy the rows for
the current version, via something like:

  DB[:content].insert([:revision, :column1, :column2, ...],
    DB[:content].select(:revision+1, :column1, :column2, ...).
      where(:revision=>CURRENT_REVISION))

With either strategy, if you want it affecting the base dataset for
the model, you just do:

  # Multiple tables
  Content.dataset = DB[:revision_023]
  # Single table with revision column
  Content.dataset = DB[:content].where(:revision=>23)

If you want to continue using the same identifier hacking, don't
alias, use super:

  DB.extend_datasets do
    def quote_identifier_append(sql, name)
      if name == :content or name == "content"
        name = Content.current_revision_table
      end
      super
    end
  end

I think a better tactic for that strategy would be to use a custom
object as the FROM table:

  crt = Object.new
  def crt.sql_literal(ds)
    Content.current_revision_table.to_s
  end
  Content.dataset = DB.from(crt)

> As a system it works really, really well but my current method for
> dynamically changing the table name within a certain context is very
> fragile and very hacky. Basically I have this monkey patch:

This design in general is very fragile and hacky. :)

> Content.dataset #=> #<Sequel::Mysql2::Dataset: "SELECT * FROM
> `content`">
>
> Content.with_revision(23) do
>   Content.dataset #=> #<Sequel::Mysql2::Dataset: "SELECT * FROM
> `revision_023`">
>
>   Content.with_revision(24) do
>     Content.dataset #=> #<Sequel::Mysql2::Dataset: "SELECT * FROM
> `revision_024`">
>
>     Content.with_editable do
>       Content.dataset #=> #<Sequel::Mysql2::Dataset: "SELECT * FROM
> `content`">
>     end
>   end
> end
>
> Does anyone know the right way to code this into a self-contained
> Sequel plugin? Any ideas greatly appreciated...

Like I said above, I think it is a bad strategy.  However, if you want
to do it anyway, assuming you don't care about threading issues, you
could use the custom FROM table object and do:

  class Content
    def with_revision(v)
      ov = current_revision_table
      self.current_revision_table = :"revision_#{sprintf('%03', v)}"
    ensure
      self.current_revision_table = ov
    end
    def with_editable
      ov = current_revision_table
      self.current_revision_table = :content
    ensure
      self.current_revision_table = ov
    end
  end

All code above is untested, use at your own risk.

Jeremy

-- 
You received this message because you are subscribed to the Google Groups 
"sequel-talk" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/sequel-talk?hl=en.

Reply via email to