{"id":47199366,"url":"https://github.com/copasetickid/draftsman","last_synced_at":"2026-03-13T12:38:57.273Z","repository":{"id":11113373,"uuid":"13470165","full_name":"copasetickid/draftsman","owner":"copasetickid","description":"Ruby gem that lets you create draft versions of your database records.","archived":false,"fork":false,"pushed_at":"2022-11-30T15:49:07.000Z","size":306,"stargazers_count":172,"open_issues_count":25,"forks_count":62,"subscribers_count":8,"default_branch":"master","last_synced_at":"2026-01-14T15:12:07.398Z","etag":null,"topics":["draft-versions","rails","ruby","ruby-gem","sinatra"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/copasetickid.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-10-10T12:06:58.000Z","updated_at":"2025-08-17T10:55:21.000Z","dependencies_parsed_at":"2023-01-13T16:20:24.024Z","dependency_job_id":null,"html_url":"https://github.com/copasetickid/draftsman","commit_stats":null,"previous_names":["jmfederico/draftsman","liveeditor/draftsman"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/copasetickid/draftsman","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/copasetickid%2Fdraftsman","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/copasetickid%2Fdraftsman/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/copasetickid%2Fdraftsman/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/copasetickid%2Fdraftsman/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/copasetickid","download_url":"https://codeload.github.com/copasetickid/draftsman/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/copasetickid%2Fdraftsman/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30467583,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-13T11:00:43.441Z","status":"ssl_error","status_checked_at":"2026-03-13T11:00:23.173Z","response_time":60,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["draft-versions","rails","ruby","ruby-gem","sinatra"],"created_at":"2026-03-13T12:38:57.188Z","updated_at":"2026-03-13T12:38:57.265Z","avatar_url":"https://github.com/copasetickid.png","language":"Ruby","readme":"# Project status #\n\n🚨 Drfatsman is [looking for a new Steward](https://github.com/jmfederico/draftsman/issues/85) 🚨\n\n# Draftsman v0.8.0.dev\n\n[![Build Status](https://travis-ci.org/jmfederico/draftsman.svg?branch=master)](https://travis-ci.org/jmfederico/draftsman)\n\nDraftsman is a Ruby gem that lets you create draft versions of your database\nrecords. If you're developing a system in need of simple drafts or a publishing\napproval queue, then Draftsman just might be what you need.\n\n-  The largest risk at this time is functionality that assists with publishing\n   or reverting dependencies through associations (for example, \"publishing\" a\n   child also publishes its parent if it's a new item). We'll be putting this\n   functionality through its paces in the coming months.\n-  The RSpec tests are lacking in some areas, so I will be adding to those over\n   time as well. (Unfortunately, this gem was not developed with TDD best\n   practices because it was lifted from PaperTrail and modified from there.)\n\nThis gem is inspired by the [Kentouzu][1] gem, which is based heavily on\n[PaperTrail][2]. In fact, much of the structure for this gem emulates PaperTrail\n(because it works beautifully). You should definitely check out PaperTrail and\nits source: it's a nice clean example of a gem that hooks into Rails and\nSinatra.\n\n## Features\n\n-  Provides API for storing drafts of creations, updates, and destroys.\n-  A max of one draft per record (via `belongs_to` association).\n-  Does not store drafts for updates that don't change anything.\n-  Allows you to specify attributes (by inclusion or exclusion) that must change\n   for a draft to be stored.\n-  Ability to query drafts based on the current drafted item, or query all\n   drafts polymorphically on the `drafts` table.\n-  `publish!` and `revert!` methods for drafts also handle any dependent drafts\n   so you don't end up with orphaned records.\n-  Allows you to get at every draft, even if the schema has since changed.\n-  Automatically records who was responsible via your controller. Draftsman\n   calls `current_user` by default if it exists, but you can have it call any\n   method you like.\n-  Allows you to store arbitrary model-level metadata with each draft (useful\n   for filtering).\n-  Allows you to store arbitrary controller-level information with each draft\n   (e.g., remote IP, current account ID).\n-  Only saves drafts when you explicitly tell it to via instance methods like\n   `save_draft` and `draft_destruction`.\n-  Stores everything in a single database table by default (generates migration\n   for you), or you can use separate tables for separate models.\n-  Supports custom draft classes so different models' drafts can have different\n   behavior.\n-  Supports custom name for `draft` association.\n-  Supports `before`, `after`, and `around` callbacks on each draft persistence\n   method, such as `before_save_draft` or `around_draft_destruction`.\n-  Threadsafe.\n\n## Compatibility\n\nCompatible with ActiveRecord 4 and 5.\n\nWorks well with Rails, Sinatra, or any other application that depends on\nActiveRecord.\n\n## Installation\n\n### Rails 4 and 5\n\nAdd Draftsman to your `Gemfile`.\n\n```ruby\ngem 'draftsman', '~\u003e 0.7.1'\n```\n\nOr if you want to grab the latest from `master`:\n\n```ruby\ngem 'draftsman', github: 'jmfederico/draftsman'\n```\n\nGenerate a migration which will add a `drafts` table to your database.\n\n    $ rails g draftsman:install\n\nYou can pass zero or any combination of these options to the generator:\n\n    $ rails g draftsman:install --skip-initializer  # Skip generation of the boilerplate initializer at\n                                                    # `config/initializers/draftsman.rb`.\n\n    $ rails g draftsman:install --with-changes      # Store changeset (diff) with each draft.\n\n    $ rails g draftsman:install --with-pg-json      # Use PostgreSQL JSON data type for serialized data.\n\nRun the migration(s).\n\n    $ rake db:migrate\n\nAdd `draft_id`, `published_at`, and `trashed_at` attributes to the models you\nwant to have drafts on. `trashed_at` is optional if you don't want to store\ndrafts for destroys.\n\n    $ rails g migration add_drafts_to_widgets draft_id:integer published_at:timestamp trashed_at:timestamp\n    $ rake db:migrate\n\nAdd `has_drafts` to the models you want to have drafts on.\n\nLastly, if your controllers have a `current_user` method, you can easily track\nwho is responsible for changes by adding a controller filter.\n\n```ruby\nclass ApplicationController\n  before_action :set_draftsman_whodunnit\nend\n```\n\n### Sinatra\n\nIn order to configure Draftsman for usage with [Sinatra][5], your Sinatra app\nmust be using `ActiveRecord` 4 or greater. It is also recommended to use the\n[Sinatra ActiveRecord Extension][6] or something similar for managing your\napplication's ActiveRecord connection in a manner similar to the way Rails does.\nIf using the aforementioned Sinatra ActiveRecord Extension, steps for setting up\nyour app with Draftsman will look something like this:\n\nAdd Draftsman to your `Gemfile`.\n\n```ruby\ngem 'draftsman', github: 'jmfederico/draftsman'\n```\n\nGenerate a migration to add a `drafts` table to your database.\n\n    $ rake db:create_migration NAME=create_drafts\n\nCopy contents of [`create_drafts.rb`][7] into the `create_drafts` migration that\nwas generated into your `db/migrate` directory.\n\nRun the migration(s).\n\n    $ rake db:migrate\n\nAdd `draft_id`, `published_at`, and `trashed_at` attributes to the models you\nwant to have drafts on. (`trashed_at` is optional if you don't want to store\ndrafts for destroys.)\n\nAdd `has_drafts` to the models you want to have drafts on.\n\nDraftsman provides a helper extension that acts similarly to the controller\nmixin it provides for Rails applications.\n\nIt will set `Draftsman::Draft#whodunnit` to whatever is returned by a method\nnamed `user_for_draftsman`, which you can define inside your Sinatra\napplication. (By default, it attempts to invoke a method named `current_user`.)\n\nIf you're using the modular [`Sinatra::Base`][8] style of application, you will\nneed to register the extension:\n\n```ruby\n# my_app.rb\nrequire 'sinatra/base'\n\nclass MyApp \u003c Sinatra::Base\n  register Draftsman::Sinatra\nend\n```\n\n## API Summary\n\n### `has_draft` Options\n\nTo get started, add a call to `has_drafts` to your model. `has_drafts` accepts\nthe following options:\n\n##### `:class_name`\n\nThe name of a custom `Draft` class. This class should inherit from\n`Draftsman::Draft`. A global default can be set for this using\n`Draftsman.draft_class_name=` if the default of `Draftsman::Draft` needs to be\noverridden.\n\n##### `:ignore`\n\nAn array of attributes for which an update to a `Draft` will not be stored if\nthey are the only ones changed.\n\n##### `:only`\n\nInverse of `ignore` - a new `Draft` will be created only for these attributes if\nsupplied. It's recommended that you only specify optional attributes for this\n(that can be empty).\n\n##### `:skip`\n\nFields to ignore completely.  As with `ignore`, updates to these fields will not\ncreate a new `Draft`. In addition, these fields will not be included in the\nserialized versions of the object whenever a new `Draft` is created.\n\n##### `:meta`\n\nA hash of extra data to store.  You must add a column to the `drafts` table for\neach key. Values are objects or `proc`s (which are called with `self`, i.e. the\nmodel with the `has_drafts`). See `Draftsman::Controller.info_for_draftsman` for\nan example of how to store data from the controller.\n\n##### `:draft`\n\nThe name to use for the `draft` association shortcut method. Default is\n`:draft`.\n\n##### `:published_at`\n\nThe name to use for the method which returns the published timestamp. Default is\n`published_at`.\n\n##### `:trashed_at`\n\nThe name to use for the method which returns the soft delete timestamp. Default\nis `trashed_at`.\n\n##### `:publish_options`\n\nThe hash of options that will be passed to `#save` when publishing the draft.\nDefault is `{ validate: false }`\n\n### Drafted Item Class Methods\n\nWhen you install the Draftsman gem, you get these methods on each model class:\n\n```ruby\n# Returns whether or not `has_draft` has been called on the model.\nWidget.draftable?\n\n# Returns whether or not a `trashed_at` timestamp is set up on this model.\nWidget.trashable?\n```\n\n### Drafted Item Instance Methods\n\nWhen you call `has_drafts` in your model, you get the following methods. See the\n\"Basic Usage\" section below for more context on where these methods fit into\nyour data's lifecycle.\n\n```ruby\n# Returns this widget's draft. You can customize the name of this association.\nwidget.draft\n\n# Returns whether or not this widget has a draft.\nwidget.draft?\n\n# Saves record and records a draft for the object's creation or update. Much\n# like `ActiveRecord`'s `#save`, returns `true` or `false` depending on whether\n# or not the objects passed validation and the save was successful.\nwidget.save_draft\n\n# Trashes object and records a draft for a `destroy` event. (The `trashed_at`\n# attribute must be set up on your model for this to work.)\nwidget.draft_destruction\n\n# Returns whether or not this item has been published at any point in its\n# lifecycle.\nwidget.published?\n\n# Returns whether or not this item has been trashed via `#draft_destruction`.\nwidget.trashed?\n```\n\n### Drafted Item Scopes\n\nYou also get these scopes added to your model for your querying enjoyment:\n\n```ruby\nWidget.drafted    # Limits to items that have drafts. Best used in an \"admin\" area in your application.\nWidget.published  # Limits to items that have been published at some point in their lifecycles. Best used in a \"public\" area in your application.\nWidget.trashed    # Limits to items that have been drafted for deletion (but not fully committed for deletion). Best used in an \"admin\" area in your application.\nWidget.live       # Limits to items that have not been drafted for deletion. Best used in an \"admin\" area in your application.\n```\n\nThese scopes optionally take a `referenced_table_name` argument for constructing\nmore advanced queries using `.includes` eager loading or `.joins`. This reduces\nambiguity both for SQL queries and for your Ruby code.\n\n```ruby\n# Query live `widgets` and `gears` without ambiguity.\nWidget.live.includes(:gears, :sprockets).live(:gears)\n```\n\n### Draft Class Methods\n\nThe `Draftsman::Draft` class has the following scopes:\n\n```ruby\n# Returns all drafts created by the `create` event.\nDraftsman::Draft.creates\n\n# Returns all drafts created by the `update` event.\nDraftsman::Draft.updates\n\n# Returns all drafts created by the `destroy` event.\nDraftsman::Draft.destroys\n```\n\n### Draft Instance Methods\n\nAnd a `Draftsman::Draft` instance has these methods:\n\n```ruby\n# Return the associated item in its state before the draft.\ndraft.item\n\n# Return the object in its state held by the draft.\ndraft.reify\n\n# Returns what changed in this draft. Similar to `ActiveModel::Dirty#changes`.\n# Returns `nil` if your `drafts` table does not have an `object_changes` text\n# column.\ndraft.changeset\n\n# Returns whether or not this is a `create` event.\ndraft.create?\n\n# Returns whether or not this is an `update` event.\ndraft.update?\n\n# Returns whether or not this is a `destroy` event.\ndraft.destroy?\n\n# Publishes this draft's associated `item`, publishes its `item`'s dependencies,\n# and destroys itself.\n# -  For `create` drafts, adds a value for the `published_at` timestamp on the\n#    item and destroys the draft.\n# -  For `update` drafts, applies the drafted changes to the item and destroys\n#    the draft.\n# -  For `destroy` drafts, destroys the item and the draft.\n#\n# Params:\n# -  A hash of options that get merged with `publish_options` defined in\n#    `has_drafts` and passed to `item.save`.\ndraft.publish!\n\n# Reverts this draft's associated `item` to its previous state, reverts its\n# `item`'s dependencies, and destroys itself.\n# -  For `create` drafts, destroys the draft and the item.\n# -  For `update` drafts, destroys the draft only.\n# -  For `destroy` drafts, destroys the draft and undoes the `trashed_at`\n#    timestamp on the item. If a draft was drafted for destroy, restores the\n#    draft.\ndraft.revert!\n\n# Returns related draft dependencies that would be along for the ride for a\n# `publish!` action.\ndraft.draft_publication_dependencies\n\n# Returns related draft dependencies that would be along for the ride for a\n# `revert!` action.\ndraft.draft_reversion_dependencies\n```\n\n### Callbacks\n\nDraftsman supports callbacks for draft saves and destroys. These callbacks can\nbe defined in any model that `has_drafts`.\n\nDraft callbacks work similarly to ActiveRecord callbacks; pass any functions\nthat you would like called before/around/after a draft persistence method.\n\nAvailable callbacks:\n```ruby\nbefore_save_draft         # called before draft is saved\naround_save_draft         # called function must yield to `save_draft`\nafter_draft_save          # called after draft is saved\n\nbefore_draft_destruction  # called before item is destroyed as a draft\naround_draft_destruction  # called function must yield to `draft_destruction`\nafter_draft_destruction   # called after item is destroyed as a draft\n```\n\nNote that callbacks must be defined after your call to `has_drafts`.\n\n## Basic Usage\n\nA basic `widgets` admin controller in Rails that saves all of the user's actions\nas drafts would look something like this. It also presents all data in its\ndrafted form, if a draft exists.\n\n```ruby\nclass Admin::WidgetsController \u003c Admin::BaseController\n  before_action :find_widget,  only: [:show, :edit, :update, :destroy]\n  before_action :reify_widget, only: [:show, :edit]\n\n  def index\n    # The `live` scope gives us widgets that aren't in the trash.\n    # It's also strongly recommended that you eagerly-load the `draft`\n    # association via `includes` so you don't keep hitting your database for\n    # each draft.\n    @widgets = Widget.live.includes(:draft).order(:title)\n\n    # Load drafted versions of each widget.\n    @widgets.map! { |widget| widget.draft.reify if widget.draft? }\n  end\n\n  def show\n  end\n\n  def new\n    @widget = Widget.new\n  end\n\n  def create\n    @widget = Widget.new(widget_params)\n\n    # Instead of calling `save`, you call `save_draft` to save it as a draft.\n    if @widget.save_draft\n      flash[:success] = 'Draft of widget saved successfully.'\n      redirect_to [:admin, @widget]\n    else\n      flash[:error] = 'There was an error saving the draft.'\n      render :new\n    end\n  end\n\n  def edit\n  end\n\n  def update\n    @widget.attributes = widget_params\n\n    # Instead of calling `update`, you call `save_draft` to save it as a draft.\n    if @widget.save_draft\n      flash[:success] = 'Draft of widget saved successfully.'\n      redirect_to [:admin, @widget]\n    else\n      flash[:error] = 'There was an error saving the draft.'\n      render :edit\n    end\n  end\n\n  def destroy\n    # Instead of calling `destroy`, you call `draft_destruction` to \"trash\" it as a draft\n    @widget.draft_destruction\n    flash[:success] = 'The widget was moved to the trash.'\n    redirect_to admin_widgets_path\n  end\n\nprivate\n\n  # Finds non-trashed widget by `params[:id]`.\n  def find_widget\n    @widget = Widget.live.find(params[:id])\n  end\n\n  # If the widget has a draft, load that version of it.\n  def reify_widget\n    @widget = @widget.draft.reify if @widget.draft?\n  end\n\n  # Strong parameters for widget form.\n  def widget_params\n    params.require(:widget).permit(:title)\n  end\nend\n```\n\nAnd \"public\" controllers (let's say read-only for this simple example) would\nignore drafts entirely via the `published` scope. This also allows items to be\n\"trashed\" for admins but still accessible to the public until that deletion is\ncommitted.\n\n```ruby\nclass WidgetsController \u003c ApplicationController\n  def index\n    # The `published` scope gives us widgets that have been committed to be\n    # viewed by non-admin users.\n    @widgets = Widget.published.order(:title)\n  end\n\n  def show\n    @widget = Widget.published.find(params[:id])\n  end\nend\n```\n\nObviously, you can use the scopes that Draftsman provides however you would like\nin any case.\n\nLastly, a `drafts` controller could be provided for admin users to see all\ndrafts, no matter the type of record (thanks to ActiveRecord's polymorphic\nassociations). From there, they could choose to revert or publish any draft\nlisted, or any other workflow action that you would like for your application to\nprovide for drafts.\n\n```ruby\nclass Admin::DraftsController \u003c Admin::BaseController\n  before_action :find_draft, only: [:show, :update, :destroy]\n\n  def index\n    @drafts = Draftsman::Draft.includes(:item).order(updated_at: :desc)\n  end\n\n  def show\n  end\n\n  # Post draft ID here to publish it\n  def update\n    # Call `draft_publication_dependencies` to check if any other drafted\n    # records should be published along with this `@draft`.\n    @dependencies = @draft.draft_publication_dependencies\n\n    # If you would like to warn the user about dependent drafts that would need\n    # to be published along with this one, you would implement an\n    # `app/views/drafts/update.html.erb` view template. In that view template,\n    # you could list the `@dependencies` and show a button posting back to this\n    # action with a name of `commit_publication`. (The button's being clicked\n    # indicates to your application that the user accepts that the dependencies\n    # should be published along with the `@draft`, thus avoiding orphaned\n    # records).\n    if @dependencies.empty? || params[:commit_publication]\n      @draft.publish!\n      flash[:success] = 'The draft was published successfully.'\n      redirect_to [:admin, :drafts]\n    else\n      # Renders `app/views/drafts/update.html.erb`\n    end\n  end\n\n  # Post draft ID here to revert it\n  def destroy\n    # Call `draft_reversion_dependencies` to check if any other drafted records\n    # should be reverted along with this `@draft`.\n    @dependencies = @draft.draft_reversion_dependencies\n\n    # If you would like to warn the user about dependent drafts that would need\n    # to be reverted along with this one, you would implement an\n    # `app/views/drafts/destroy.html.erb` view template. In that view template,\n    # you could list the `@dependencies` and show a button posting back to this\n    # action with a name of `commit_reversion`. (The button's being clicked\n    # indicates to your application that the user accepts that the dependencies\n    # should be reverted along with the `@draft`, thus avoiding orphaned\n    # records).\n    if @dependencies.empty? || params[:commit_reversion]\n      @draft.revert!\n      flash[:success] = 'The draft was reverted successfully.'\n      redirect_to [:admin, :drafts]\n    else\n      # Renders `app/views/drafts/destroy.html.erb`\n    end\n  end\n\nprivate\n\n  # Finds draft by `params[:id]`.\n  def find_draft\n    @draft = Draftsman::Draft.find(params[:id])\n  end\nend\n\n```\n\nIf you would like your `Widget` to have callbacks, it might look something like this:\n\n```ruby\nclass Widget \u003c ActiveRecord::Base\n  has_drafts\n\n  before_save_draft :say_hi\n  around_save_draft :surround_update\n\nprivate\n\n  def say_hi\n    self.some_attr = 'Hi!'\n  end\n\n  def surround_update\n    if self.persisted?\n      # do something before update\n      yield\n      # do something after update\n    else\n      yield\n    end\n  end\nend\n```\n\n\n## Differences from PaperTrail\n\nIf you are familiar with the PaperTrail gem, some parts of the Draftsman gem\nwill look very familiar.\n\nHowever, there are some differences:\n\n*  PaperTrail hooks into ActiveRecord callbacks so that versions can be saved\n   automatically with your normal CRUD operations (`#save`, `#create`,\n   `#update`, `#destroy`, etc.). Draftsman requires that you explicitly call its\n   own CRUD methods in order to save a draft (`#save_draft` and\n   `draft_destruction`).\n\n*  PaperTrail's `Version#object` column looks \"backward\" and records the\n   object's state _before_ the changes occurred. Because drafts record changes\n   as they will look in the future, they must work differently. Draftsman's\n   `Draft#object` records the object's state _after_ changes are applied to the\n   master object. *But* `destroy` drafts record the object as it was _before_ it\n   was destroyed (in case you want the option of reverting the destroy later and\n   restoring the drafted item back to its original state).\n\n## Semantic Versioning\n\nLike many Ruby gems, Draftsman honors the concepts behind\n[semantic versioning][10]:\n\n\u003e Given a version number MAJOR.MINOR.PATCH, increment the:\n\u003e\n\u003e 1.  MAJOR version when you make incompatible API changes,\n\u003e 2.  MINOR version when you add functionality in a backwards-compatible manner, and\n\u003e 3.  PATCH version when you make backwards-compatible bug fixes.\n\n## Contributing\n\nIf you feel like you can add something useful to Draftsman, then don't hesitate\nto contribute! To make sure your fix/feature has a high chance of being\nincluded, please do the following:\n\n1.  Fork the repo.\n\n2.  Run `bundle install`.\n\n3.  Run `RAILS_ENV=test bundle exec rake -f spec/dummy/Rakefile db:schema:load`\n    to load test database schema.\n\n4.  Add at least one test for your change. Only refactoring and documentation\n    changes require no new tests. If you are adding functionality or fixing a\n    bug, you need a test!\n\n5.  Make all tests pass by running `rake`.\n\n6.  Push to your fork and submit a pull request.\n\nI can't guarantee that I will accept the change, but if I don't, I will be sure\nto let you know why.\n\nHere are some things that will increase the chance that your pull request is\naccepted, taken straight from the Ruby on Rails guide:\n\n-  Use Rails idioms\n-  Include tests that fail without your code, and pass with it\n-  Update the documentation, guides, or whatever is affected by your\n   contribution\n\nThis gem is a work in progress. I am adding specs as I need features in my\napplication. Please add missing ones as you work on features or find bugs!\n\n## License\n\nCopyright 2013-2016 Minimal Orange, LLC.\n\nDraftsman is released under the [MIT License][9].\n\n\n[1]: https://github.com/seaneshbaugh/kentouzu\n[2]: https://github.com/airblade/paper_trail\n[4]: http://railscasts.com/episodes/416-form-objects\n[5]: http://www.sinatrarb.com/\n[6]: https://github.com/janko-m/sinatra-activerecord\n[7]: https://raw.github.com/jmfederico/draftsman/master/lib/generators/draftsman/templates/create_drafts.rb\n[8]: http://www.sinatrarb.com/intro.html#Modular%20vs.%20Classic%20Style\n[9]: http://www.opensource.org/licenses/MIT\n[10]: http://semver.org/\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcopasetickid%2Fdraftsman","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcopasetickid%2Fdraftsman","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcopasetickid%2Fdraftsman/lists"}