Conscript
Provides ActiveRecord models with a drafts scope, and the functionality to create draft instances and publish them.
Existing instances may have one or more draft versions which are initially created by duplication, including any required associations. A draft may then be published, overwriting the original instance.
Alternatively, draft instances may be created from scratch and published later.
The approach of the gem differs from others in that it does not create extra tables or serialise objects; instead it uses ActiveRecord's built-in scoping so that drafts have all of the same functionality as any other instance.
Installation
Add this line to your application's Gemfile:
gem 'conscript'
And then execute:
$ bundle
Or install it yourself as:
$ gem install conscript
Usage
All models which wish to register for the draft must add two columns to their database schema, as illustrated in this example migration:
class AddDraftColumns < ActiveRecord::Migration
def self.up
add_column :table_name, :draft_parent_id, :integer
add_column :table_name, :is_draft, :boolean, default: false
add_index :table_name, :draft_parent_id
add_index :table_name, :is_draft
end
def self.down
remove_column :table_name, :draft_parent_id
remove_column :table_name, :is_draft
end
end
To use the drafts functionality, call register_for_draft in your ActiveRecord model:
class Article < ActiveRecord::Base
register_for_draft
end
A default_scope is then set to exclude all draft instances from finders. You may use the drafts scope to access them:
Article.drafts
If you need to access drafts and original instances together, use unscoped as you would any other default_scope:
Article.unscoped.all
Instance methods
Draft instances may optionally be created from existing instances:
article = Article.first
draft = article.save_as_draft!
Or you can create drafts from scratch:
Article.new.save_as_draft!
You can access the original instance from the draft:
draft.draft_parent == article # => true
And you can also access all of an instance's drafts:
article.drafts # => [draft]
To determine whether an instance is a draft:
article.is_draft? # => false
draft.is_draft? # => true
Publish a draft to overwrite the original instance:
draft.publish_draft # returns the original, updated instance
Options
By default, drafts are created with ActiveRecord::Base#dup, i.e. a shallow copy of all attributes, without any associations.
Options may be passed to the register_for_draft method, e.g:
register_for_draft associations: :tags, ignore_attributes: :cached_slug
A list of options are below:
:associationsan array ofhas_manyassociation names to duplicate. These will be copied to the draft and overwrite the original instance's when published. Deep cloning is possible thanks to thedeep_cloneablegem. Refer to thedeep_cloneabledocumentation to get an idea of how far you can go with this. Please note:belongs_toassociations aren't supported as these should be drafted separately.:ignore_attributesan array of attribute names which should not be duplicated. Timestamps and STItypecolumns are excluded by default. Don't include association names here.
Using with CarrierWave
Conscript supports CarrierWave uploads, but there's a couple of things you should be aware of.
First, you must ensure register_for_draft is called after any calls to mount_uploader.
Then, in your uploaders where store_dir is defined, if you are organising file storage by model instance (e.g. #to_param) then you should use the new model method uploader_store_param to define the unique location, e.g:
class ArticleImageUploader < ImageUploader
def store_dir
"public/images/articles/#{model.uploader_store_param}"
end
end
This will result in uploads for drafts being stored in the same location as the original instance. This is because Conscript does not want to have to worry about moving files when publishing an instance.
Conscript also overrides CarrierWave's #destroy callbacks to ensure that no other instance is using the same file before deleting it from the filesystem. Otherwise this can happen when you delete a draft with the same file as the original instance.
Limitations
For reasons of sanity:
- You cannot make changes to an instance if it has drafts, this is because it would be difficult to propogate those changes down or provide visibility of the changes.
- When you publish a draft, any other drafts for the same
draft_parentare also destroyed.
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create new Pull Request