0.0
No commit activity in last 3 years
No release in over 3 years
Create clones of CouchDB documents, and schedule them for publication.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

= 1.0.1
~> 0.1.1
~> 0.1.1
 Project Readme

CouchCloner

Clone your CouchDB CouchRest::Model::Base documents and schedule them for publishing.

Installation

It's a ruby gem called couch_cloner. Install it.

Setup

Simply include the module CouchCloner into your CouchRest::Model::Base document. For example:

class HtmlSnippet < CouchRest::Model::Base
  include CouchCloner
end

Setup complete.

Cloning (.clone/.clone!)

Let's imagine we'd like to create an HtmlSnippet that appears on the home page of our website. Our model definition might look like this:

class HtmlSnippet < CouchRest::Model::Base
  include CouchCloner

  property :content

  timestamps!
end

And our snippet might look like this:

homepage = HtmlSnippet.create :clone_id => "homepage", :content => "<h1>Homepage!</h1>"

OK, so that's a pretty lame bit of content, but did you notice the clone_id? If you're going to clone, you have to set a clone_id on your document first. The clone_id is the shared identifier between all of your clones. That's important; you'll see why in a few moments. Read on.

So now your content administrators want to use your CMS to schedule clones of this content to publish on the site several days in advance. How do we clone it?

We can create a soft clone (i.e., a new HtmlSnippet based on the original, but not yet persisted to the database) via the clone method:

next = homepage.clone

next.content      #==> "<h1>Homepage!</h1>"
next.clone_id     #==> "homepage"
next.new_record?  #==> true

We can create a persisted clone with the clone! method:

next = homepage.clone!

next.content      #==> "<h1>Homepage!</h1>"
next.clone_id     #==> "homepage"
next.new_record?  #==> false

Note that, when cloned, the start scheduling property of the clone is not copied. See the next section for details about how scheduling works.

Scheduling (.start)

The utility of these clones is most apparent when you schedule multiple clones. The scheduling has only one constraint: each document in a clone group must have a unique date/time stamp, or nil.

Returning to our previous example of next and homepage HtmlSnippet clones:

homepage.start = Time.now.beginning_of_day
homepage.save #==> true

We've now scheduled the original clone to start at the beginning of today.

Next, we'll try to schedule the next clone for the same time:

next.start = Time.now.beginning_of_day
next.save #==> false
next.errors[:start] #==> "must be unique"

Since we need to create a unique timestamp, we'll schedule next to start tomorrow:

next.start = 1.day.from_now
next.save #==> true
next.errors.empty? #==> true

We could proceed creating and scheduling clones like this ad infinitum.

Retrieving all the clones in a clone_id group (.map_by_clone_id/.count_by_clone_id)

To retrieve all of the clones with the same clone_id, call the .map_by_clone_id query proxy and provide it with a clone_id key that you want to look up:

HtmlSnippet.map_by_clone_id.key("some_clone_id").get!

If you're unfamiliar with this syntax, read up on the couch_view gem at http://github.com/moonmaster9000/couch_view

This will return all clones with that clone_id. You can pass all of the usual map/reduce options to this method (e.g., limit/skip):

HtmlSnippet.map_by_clone_id.key("some_clone_id").limit(10).skip(10).get!

Clones with the same clone_id will be sorted by their _id (this is simply how CouchDB works).

A more useful sorting option is to have the documents sorted by their start property. Clones with a start of nil or "" will sort last, and will order by their created_at property. Thus, it's as if the clones with a start time are assumed to be scheduled at time infinity + created_at. Pretty cool, right?

To get all the clones with the same clone_id sorted in this order, call map_by_clone_id_and_start:

HtmlSnippet.map_by_clone_id_and_start("some_clone_id")

This will return a query proxy with a startkey of ["some_clone_id"], and an endkey of ["some_clone_id", {:end => true}], which will select all of the documents with clone id "some_clone_id", sorted by their "start" time.

You can pass off all of the usual map/reduce options to this view:

HtmlSnippet.map_by_clone_id_and_start("some_clone_id").limit(10).skip(1).get!

If you'd like to retrieve only a subset of this view, you can use the :key map/reduce option. For example, suppose we'd like to see all clones scheduled to start after now:

HtmlSnippet.map_by_clone_id_and_start("some_clone_id", Time.now)

The endkey will still be automatically defaulted to ["some_clone_id", {:end => nil}]

Lastly, you can find the total number of clones with the same clone_id by calling the count_by_clone_id class method on your model:

HtmlSnippet.count_by_clone_id("some_clone_id")

If you wanted to count only a subset of your clones based on their start time, you can use count_by_clone_id_and_start:

HtmlSnippet.count_by_clone_id_and_start("some_clone_id", Time.now)

Again, the endkey will be automatically defaulted to ["some_clone_id", {:end => nil}]

Retrieving the active clone by clone_id (.map_active_by_clone_id)

Now that we've created an original clone scheduled today, and a next clone scheduled tomorrow, let's determine which one is currently active:

HtmlSnippet.map_active_by_clone_id("homepage").get!.first.content #==> "<h1>this is awesome</h1>"

The map_active_by_clone_id method accepts a clone_id (in our case a label), and returns a query proxy that will return either zero or one results (zero if no currently active HtmlSnippet is found with that label, otherwise, one for the currently active HtmlSnippet).

Retrieving clones scheduled into the future (.map_future_by_clone_id)

We can get a list of the future clones by label via the map_future_by_clone_id method:

past = HtmlSnippet.create :clone_id => "homepage", :start => 1.day.ago
next = HtmlSnippet.create :clone_id => "homepage", :start => 1.day.from_now

HtmlSnippet.map_future_by_clone_id("homepage").get!
  #==> returns the homepage snippet "next" that starts one day from now

This is essentially a shortcut for:

HtmlSnippet.map_by_clone_id_and_start("homepage", Time.now)

If we create a clone with a start of nil, they will show up sorted at the end of map_future_by_clone_id:

future = next.clone! #==> remember, on clone, the `start` property is not copied
future.start.should == nil
future.save

HtmlSnippet.map_future_by_clone_id("homepage").get!
  #==> would return an array consisting of the `next` clone followed by the `future` clone

If there are multiple clones with a start of nil, they will sort by their created_at timestamp.

We also provide a method for counting the number of active and future clones in a given clone_id group:

HtmlSnippet.count_future_by_clone_id("some_clone_id").get!

Retrieving past clones (.map_past_by_clone_id)

You might find it useful to retrieve only the clones scheduled in the past. You can use the map_past_by_clone_id method:

HtmlSnippet.map_past_by_clone_id("some_clone_id").get!

They will be ordered by their start date.

You can also count the number of past clones via the count_past_by_clone_id:

HtmlSnippet.count_past_by_clone_id("some_clone_id").get!

Retreiving the list of currently used clone_ids (.map_clone_ids)

You can retrieve an array of all of the clone_id's in use by calling the map_clone_ids method on your model:

HtmlSnippet.database.recreate!
HtmlSnippet.create :clone_id => "homepage"
HtmlSnippet.create :clone_id => "contact_us"
HtmlSnippet.create :clone_id => "news"

HtmlSnippet.map_clone_ids.get!['rows'].map {|row| row['key']} 
  #==> ["contact_us", "homepage", "news"]

You can use all of the map/reduce options you're used to (e.g., limit/skip):

HtmlSnippet.map_clone_ids.limit(1).skip(1).get!['rows'].map {|row| row['key']}  
  #==> ["homepage"] 

You can also get a count of all clone_ids:

HtmlSnippet.count_clone_ids! 
  #==> 3

Retreiving the clone created farthest in the future for a clone_id group (.map_last_future_by_clone_id)

If you'd like to retrieve the latest clone within a clone group, you could of course call future_clones_by_clone_id and then call last on the resulting array - however, that would be quite silly and idiotically inefficiant. So, instead, call last_future_by_clone_id:

snippet_1 = HtmlSnippet.create :clone_id => "snippety", :start => Time.now
snippet_2 = HtmlSnippet.create :clone_id => "snippety", :start => 1000.years.from_now

HtmlSnippet.map_last_future_by_clone_id("snippety").get!.first.should == snippet_2

After creating these two snippet's, calling HtmlSnippet.last_future_by_clone_id "snippety" would return snippet_2. However, if we create another "snippety" snippet without a start date:

snippet_3 = HtmlSnippet.create :clone_id => "snippety"

HtmlSnippet.map_last_future_by_clone_id("snippety").get!.first.should == snippet_3

Then calling HtmlSnippet.last_future_by_clone_id "snippety" would return snippet3. Basically, you can imagine clones with a null start date or an empty string start date to have a start scheduled for infinity + created_at; in other words, they sort at the end of the map of clones in a clone_id group, and if there are multiple clones without a start date, then they sort by created at (still at the end of the map).

CouchPublish Integration

The couch_cloner gem integrates nicely with the couch_publish gem.

If you include CouchCloner into a gem that already includes CouchPublish, then you can use published and unpublished query modifiers on your query proxies:

class HtmlSnippet < CouchRest::Model::Base
  include CouchPublish
  include CouchCloner

  # etc...
end

HtmlSnippet.map_by_clone_id.key("some-clone-id").published.get!
HtmlSnippet.count_by_clone_id.key("some-clone-id").unpublished.get!
HtmlSnippet.map_active_by_clone_id("some-clone-id").published.get!.first
HtmlSnippet.map_by_clone_id_and_start("some_clone_id")
HtmlSnippet.map_active_and_future_clones_by_clone_id("some-clone-id").unpublished.get!.first
HtmlSnippet.map_last_future_by_clone_id("some-clone-id").published.get!.first
HtmlSnippet.clone_ids.unpublished.get!
HtmlSnippet.count_clone_ids.published.get!

CouchVisible Integration

The couch_cloner gem integrates nicely with the couch_visible gem.

If you include CouchCloner into a gem that already includes CouchVisible, then you can pass :shown => true and :hidden => true options to your CouchCloner query methods:

class HtmlSnippet < CouchRest::Model::Base
  include CouchVisible
  include CouchCloner

  # etc...
end

HtmlSnippet.map_by_clone_id.key("some-clone-id").shown.get!
HtmlSnippet.count_by_clone_id.key("some-clone-id").hidden.get!
HtmlSnippet.map_active_by_clone_id("some-clone-id").shown.get!.first
HtmlSnippet.map_by_clone_id_and_start("some_clone_id")
HtmlSnippet.map_active_and_future_clones_by_clone_id("some-clone-id").hidden.get!.first
HtmlSnippet.map_last_future_by_clone_id("some-clone-id").shown.get!.first
HtmlSnippet.clone_ids.hidden.get!
HtmlSnippet.count_clone_ids.shown.get!

CouchPublish and CouchVisible Integration

If you include CouchCloner into a gem that already includes both CouchVisible and CouchPublish, then you can, of course, mix and match unpublished, published, shown, and hidden query modifiers in your CouchCloner query methods:

class HtmlSnippet < CouchRest::Model::Base
  include CouchPublish
  include CouchVisible
  include CouchCloner

  # etc...
end

HtmlSnippet.map_by_clone_id.key("some-clone-id").shown.published.get!
HtmlSnippet.count_by_clone_id.key("some-clone-id").hidden.get!
HtmlSnippet.map_active_by_clone_id("some-clone-id").shown.get!.first
HtmlSnippet.map_by_clone_id_and_start("some_clone_id")
HtmlSnippet.map_active_and_future_clones_by_clone_id("some-clone-id").hidden.published.get!.first
HtmlSnippet.map_last_future_by_clone_id("some-clone-id").shown.get!.first
HtmlSnippet.clone_ids.hidden.get!
HtmlSnippet.count_clone_ids.shown.unpublished.get!

PUBLIC DOMAIN

This software is public domain. By contributing to it, you agree to let your code contribution enter the public domain.