draftsman 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -1,64 +1,76 @@
1
- # Draftsman v0.5.1 (beta)
1
+ # Draftsman v0.6.0
2
2
 
3
- Draftsman is a Ruby gem that lets you create draft versions of your database records. If you're developing a system in
4
- need of simple drafts or a publishing approval queue, then Draftsman just might be what you need.
3
+ [![Build Status](https://secure.travis-ci.org/liveeditor/draftsman.svg?branch=master)](http://travis-ci.org/liveeditor/draftsman)
5
4
 
6
- **This gem is still considered experimental**, so proceed with caution.
5
+ Draftsman is a Ruby gem that lets you create draft versions of your database
6
+ records. If you're developing a system in need of simple drafts or a publishing
7
+ approval queue, then Draftsman just might be what you need.
7
8
 
8
- * The largest risk at this time is functionality that assists with publishing or reverting dependencies through
9
- associations (for example, "publishing" a child also publishes its parent if it's a new item). I'll be putting this
9
+ - The largest risk at this time is functionality that assists with publishing
10
+ or reverting dependencies through associations (for example, "publishing" a
11
+ child also publishes its parent if it's a new item). We'll be putting this
10
12
  functionality through its paces in the coming months.
11
- * The RSpec tests are lacking in some areas, so I will be adding to those over time as well. (Unfortunately, this gem
12
- was not developed with TDD best practices because it was lifted from PaperTrail and modified from there.)
13
+ - The RSpec tests are lacking in some areas, so I will be adding to those over
14
+ time as well. (Unfortunately, this gem was not developed with TDD best
15
+ practices because it was lifted from PaperTrail and modified from there.)
13
16
 
14
- This gem is inspired by the [Kentouzu][1] gem, which is based heavily on [PaperTrail][2]. In fact, much of the structure
15
- for this gem emulates PaperTrail (because it works beautifully). You should definitely check out PaperTrail and its
16
- source: it's a nice clean example of a gem that hooks into Rails and Sinatra.
17
+ This gem is inspired by the [Kentouzu][1] gem, which is based heavily on
18
+ [PaperTrail][2]. In fact, much of the structure for this gem emulates PaperTrail
19
+ (because it works beautifully). You should definitely check out PaperTrail and
20
+ its source: it's a nice clean example of a gem that hooks into Rails and
21
+ Sinatra.
17
22
 
18
23
  ## Features
19
24
 
20
25
  - Provides API for storing drafts of creations, updates, and destroys.
21
26
  - A max of one draft per record (via `belongs_to` association).
22
27
  - Does not store drafts for updates that don't change anything.
23
- - Allows you to specify attributes (by inclusion or exclusion) that must change for a draft to be stored.
24
- - Ability to query drafts based on the current drafted item, or query all drafts polymorphically on the `drafts` table.
25
- - `publish!` and `revert!` methods for drafts also handle any dependent drafts so you don't end up with orphaned
26
- records.
28
+ - Allows you to specify attributes (by inclusion or exclusion) that must change
29
+ for a draft to be stored.
30
+ - Ability to query drafts based on the current drafted item, or query all
31
+ drafts polymorphically on the `drafts` table.
32
+ - `publish!` and `revert!` methods for drafts also handle any dependent drafts
33
+ so you don't end up with orphaned records.
27
34
  - Allows you to get at every draft, even if the schema has since changed.
28
- - Automatically records who was responsible via your controller. Draftsman calls `current_user` by default if it
29
- exists, but you can have it call any method you like.
30
- - Allows you to store arbitrary model-level metadata with each draft (useful for filtering).
31
- - Allows you to store arbitrary controller-level information with each draft (e.g., remote IP, current account ID).
32
- - Only saves drafts when you explicitly tell it to via instance methods like `draft_creation`, `draft_update`, and
33
- `draft_destruction`.
34
- - Stores everything in a single database table by default (generates migration for you), or you can use separate tables
35
- for separate models.
36
- - Supports custom draft classes so different models' drafts can have different behavior.
35
+ - Automatically records who was responsible via your controller. Draftsman
36
+ calls `current_user` by default if it exists, but you can have it call any
37
+ method you like.
38
+ - Allows you to store arbitrary model-level metadata with each draft (useful
39
+ for filtering).
40
+ - Allows you to store arbitrary controller-level information with each draft
41
+ (e.g., remote IP, current account ID).
42
+ - Only saves drafts when you explicitly tell it to via instance methods like
43
+ `save_draft` and `draft_destruction`.
44
+ - Stores everything in a single database table by default (generates migration
45
+ for you), or you can use separate tables for separate models.
46
+ - Supports custom draft classes so different models' drafts can have different
47
+ behavior.
37
48
  - Supports custom name for `draft` association.
38
- - Supports `before`, `after`, and `around` callbacks on each draft persistence method, such as `before_draft_creation`
39
- or `around_draft_update`.
49
+ - Supports `before`, `after`, and `around` callbacks on each draft persistence
50
+ method, such as `before_save_draft` or `around_draft_destruction`.
40
51
  - Threadsafe.
41
52
 
42
53
  ## Compatibility
43
54
 
44
- Compatible with ActiveRecord 3, 4, and 5.
55
+ Compatible with ActiveRecord 4 and 5.
45
56
 
46
- Works well with Rails, Sinatra, or any other application that depends on ActiveRecord.
57
+ Works well with Rails, Sinatra, or any other application that depends on
58
+ ActiveRecord.
47
59
 
48
60
  ## Installation
49
61
 
50
- ### Rails 3, 4, and 5
62
+ ### Rails 4 and 5
51
63
 
52
64
  Add Draftsman to your `Gemfile`.
53
65
 
54
66
  ```ruby
55
- gem 'draftsman', '~> 0.5.1'
67
+ gem 'draftsman', '~> 0.6.0'
56
68
  ```
57
69
 
58
70
  Or if you want to grab the latest from `master`:
59
71
 
60
72
  ```ruby
61
- gem 'draftsman', :github => 'liveeditor/draftsman'
73
+ gem 'draftsman', github: 'liveeditor/draftsman'
62
74
  ```
63
75
 
64
76
  Generate a migration which will add a `drafts` table to your database.
@@ -78,16 +90,17 @@ Run the migration(s).
78
90
 
79
91
  $ rake db:migrate
80
92
 
81
- Add `draft_id`, `published_at`, and `trashed_at` attributes to the models you want to have drafts on. `trashed_at` is
82
- optional if you don't want to store drafts for destroys.
93
+ Add `draft_id`, `published_at`, and `trashed_at` attributes to the models you
94
+ want to have drafts on. `trashed_at` is optional if you don't want to store
95
+ drafts for destroys.
83
96
 
84
97
  $ rails g migration add_drafts_to_widgets draft_id:integer published_at:timestamp trashed_at:timestamp
85
98
  $ rake db:migrate
86
99
 
87
100
  Add `has_drafts` to the models you want to have drafts on.
88
101
 
89
- Lastly, if your controllers have a `current_user` method, you can easily track who is responsible for changes by
90
- adding a controller filter.
102
+ Lastly, if your controllers have a `current_user` method, you can easily track
103
+ who is responsible for changes by adding a controller filter.
91
104
 
92
105
  ```ruby
93
106
  class ApplicationController
@@ -97,39 +110,45 @@ end
97
110
 
98
111
  ### Sinatra
99
112
 
100
- In order to configure Draftsman for usage with [Sinatra][5], your Sinatra app must be using `ActiveRecord` 3 or greater.
101
- It is also recommended to use the [Sinatra ActiveRecord Extension][6] or something similar for managing your
102
- application's ActiveRecord connection in a manner similar to the way Rails does. If using the aforementioned Sinatra
103
- ActiveRecord Extension, steps for setting up your app with Draftsman will look something like this:
113
+ In order to configure Draftsman for usage with [Sinatra][5], your Sinatra app
114
+ must be using `ActiveRecord` 4 or greater. It is also recommended to use the
115
+ [Sinatra ActiveRecord Extension][6] or something similar for managing your
116
+ application's ActiveRecord connection in a manner similar to the way Rails does.
117
+ If using the aforementioned Sinatra ActiveRecord Extension, steps for setting up
118
+ your app with Draftsman will look something like this:
104
119
 
105
120
  Add Draftsman to your `Gemfile`.
106
121
 
107
122
  ```ruby
108
- gem 'draftsman', :github => 'live-editor/draftsman'
123
+ gem 'draftsman', github: 'liveeditor/draftsman'
109
124
  ```
110
125
 
111
126
  Generate a migration to add a `drafts` table to your database.
112
127
 
113
128
  $ rake db:create_migration NAME=create_drafts
114
129
 
115
- Copy contents of [`create_drafts.rb`][7] into the `create_drafts` migration that was generated into your `db/migrate`
116
- directory.
130
+ Copy contents of [`create_drafts.rb`][7] into the `create_drafts` migration that
131
+ was generated into your `db/migrate` directory.
117
132
 
118
133
  Run the migration(s).
119
134
 
120
135
  $ rake db:migrate
121
136
 
122
- Add `draft_id`, `published_at`, and `trashed_at` attributes to the models you want to have drafts on. (`trashed_at` is
123
- optional if you don't want to store drafts for destroys.)
137
+ Add `draft_id`, `published_at`, and `trashed_at` attributes to the models you
138
+ want to have drafts on. (`trashed_at` is optional if you don't want to store
139
+ drafts for destroys.)
124
140
 
125
141
  Add `has_drafts` to the models you want to have drafts on.
126
142
 
127
- Draftsman provides a helper extension that acts similar to the controller mixin it provides for Rails applications.
143
+ Draftsman provides a helper extension that acts similarly to the controller
144
+ mixin it provides for Rails applications.
128
145
 
129
- It will set `Draftsman.whodunnit` to whatever is returned by a method named `user_for_paper_trail`, which you can define
130
- inside your Sinatra Application. (By default, it attempts to invoke a method named `current_user`.)
146
+ It will set `Draftsman::Draft#whodunnit` to whatever is returned by a method
147
+ named `user_for_paper_trail`, which you can define inside your Sinatra
148
+ application. (By default, it attempts to invoke a method named `current_user`.)
131
149
 
132
- If you're using the modular [`Sinatra::Base`][8] style of application, you will need to register the extension:
150
+ If you're using the modular [`Sinatra::Base`][8] style of application, you will
151
+ need to register the extension:
133
152
 
134
153
  ```ruby
135
154
  # my_app.rb
@@ -144,39 +163,54 @@ end
144
163
 
145
164
  ### `has_draft` Options
146
165
 
147
- To get started, add a call to `has_drafts` to your model. `has_drafts` accepts the following options:
166
+ To get started, add a call to `has_drafts` to your model. `has_drafts` accepts
167
+ the following options:
148
168
 
149
169
  ##### `:class_name`
150
170
 
151
- The name of a custom `Draft` class. This class should inherit from `Draftsman::Draft`. A global default can be
152
- set for this using `Draftsman.draft_class_name=` if the default of `Draftsman::Draft` needs to be overridden.
171
+ The name of a custom `Draft` class. This class should inherit from
172
+ `Draftsman::Draft`. A global default can be set for this using
173
+ `Draftsman.draft_class_name=` if the default of `Draftsman::Draft` needs to be
174
+ overridden.
153
175
 
154
176
  ##### `:ignore`
155
177
 
156
- An array of attributes for which an update to a `Draft` will not be stored if they are the only ones changed.
178
+ An array of attributes for which an update to a `Draft` will not be stored if
179
+ they are the only ones changed.
157
180
 
158
181
  ##### `:only`
159
- Inverse of `ignore` - a new `Draft` will be created only for these attributes if supplied. It's recommended that
160
- you only specify optional attributes for this (that can be empty).
182
+
183
+ Inverse of `ignore` - a new `Draft` will be created only for these attributes if
184
+ supplied. It's recommended that you only specify optional attributes for this
185
+ (that can be empty).
161
186
 
162
187
  ##### `:skip`
163
- Fields to ignore completely. As with `ignore`, updates to these fields will not create a new `Draft`. In
164
- addition, these fields will not be included in the serialized versions of the object whenever a new `Draft` is
165
- created.
188
+
189
+ Fields to ignore completely. As with `ignore`, updates to these fields will not
190
+ create a new `Draft`. In addition, these fields will not be included in the
191
+ serialized versions of the object whenever a new `Draft` is created.
166
192
 
167
193
  ##### `:meta`
168
- A hash of extra data to store. You must add a column to the `drafts` table for each key. Values are objects or
169
- `proc`s (which are called with `self`, i.e. the model with the `has_drafts`). See
170
- `Draftsman::Controller.info_for_draftsman` for an example of how to store data from the controller.
194
+
195
+ A hash of extra data to store. You must add a column to the `drafts` table for
196
+ each key. Values are objects or `proc`s (which are called with `self`, i.e. the
197
+ model with the `has_drafts`). See `Draftsman::Controller.info_for_draftsman` for
198
+ an example of how to store data from the controller.
171
199
 
172
200
  ##### `:draft`
173
- The name to use for the `draft` association shortcut method. Default is `:draft`.
201
+
202
+ The name to use for the `draft` association shortcut method. Default is
203
+ `:draft`.
174
204
 
175
205
  ##### `:published_at`
176
- The name to use for the method which returns the published timestamp. Default is `published_at`.
206
+
207
+ The name to use for the method which returns the published timestamp. Default is
208
+ `published_at`.
177
209
 
178
210
  ##### `:trashed_at`
179
- The name to use for the method which returns the soft delete timestamp. Default is `trashed_at`.
211
+
212
+ The name to use for the method which returns the soft delete timestamp. Default
213
+ is `trashed_at`.
180
214
 
181
215
  ### Drafted Item Class Methods
182
216
 
@@ -192,8 +226,9 @@ Widget.trashable?
192
226
 
193
227
  ### Drafted Item Instance Methods
194
228
 
195
- When you call `has_drafts` in your model, you get the following methods. See the "Basic Usage" section below for more
196
- context on where these methods fit into your data's lifecycle.
229
+ When you call `has_drafts` in your model, you get the following methods. See the
230
+ "Basic Usage" section below for more context on where these methods fit into
231
+ your data's lifecycle.
197
232
 
198
233
  ```ruby
199
234
  # Returns this widget's draft. You can customize the name of this association.
@@ -202,23 +237,20 @@ widget.draft
202
237
  # Returns whether or not this widget has a draft.
203
238
  widget.draft?
204
239
 
205
- # Creates object and records a draft for the object's creation. Returns `true` or `false` depending on whether or not
206
- # the objects passed validation and the save was successful.
207
- widget.draft_creation
208
-
209
- # Updates object and records a draft for an `update` event. If the draft is being updated to the object's original
210
- # state, the draft is destroyed. Returns `true` or `false` depending on if the object passed validation and the save
211
- # was successful.
212
- widget.draft_update
240
+ # Saves record and records a draft for the object's creation or update. Much
241
+ # like `ActiveRecord`'s `#save`, returns `true` or `false` depending on whether
242
+ # or not the objects passed validation and the save was successful.
243
+ widget.save_draft
213
244
 
214
- # Trashes object and records a draft for a `destroy` event. (The `trashed_at` attribute must be set up on your model for
215
- # this to work.)
245
+ # Trashes object and records a draft for a `destroy` event. (The `trashed_at`
246
+ # attribute must be set up on your model for this to work.)
216
247
  widget.draft_destruction
217
248
 
218
- # Returns whether or not this item has been published at any point in its lifecycle.
249
+ # Returns whether or not this item has been published at any point in its
250
+ # lifecycle.
219
251
  widget.published?
220
252
 
221
- # Returns whether or not this item has been trashed via `draft_destruction`.
253
+ # Returns whether or not this item has been trashed via `#draft_destruction`.
222
254
  widget.trashed?
223
255
  ```
224
256
 
@@ -233,17 +265,18 @@ Widget.trashed # Limits to items that have been drafted for deletion (but not
233
265
  Widget.live # Limits to items that have not been drafted for deletion. Best used in an "admin" area in your application.
234
266
  ```
235
267
 
236
- These scopes optionally take a `referenced_table_name` argument for constructing more advanced queries using `includes`
237
- eager loading or `joins`. This reduces ambiguity both for SQL queries and for your Ruby code.
268
+ These scopes optionally take a `referenced_table_name` argument for constructing
269
+ more advanced queries using `.includes` eager loading or `.joins`. This reduces
270
+ ambiguity both for SQL queries and for your Ruby code.
238
271
 
239
272
  ```ruby
240
- # Query live widgets and gears without ambiguity.
273
+ # Query live `widgets` and `gears` without ambiguity.
241
274
  Widget.live.includes(:gears, :sprockets).live(:gears)
242
275
  ```
243
276
 
244
277
  ### Draft Class Methods
245
278
 
246
- The `Draftsman::Draft` class has the following methods:
279
+ The `Draftsman::Draft` class has the following scopes:
247
280
 
248
281
  ```ruby
249
282
  # Returns all drafts created by the `create` event.
@@ -264,11 +297,12 @@ And a `Draftsman::Draft` instance has these methods:
264
297
  # Return the associated item in its state before the draft.
265
298
  draft.item
266
299
 
267
- # Return the object held by the draft.
300
+ # Return the object in its state held by the draft.
268
301
  draft.reify
269
302
 
270
303
  # Returns what changed in this draft. Similar to `ActiveModel::Dirty#changes`.
271
- # Returns `nil` if your `drafts` table does not have an `object_changes` text column.
304
+ # Returns `nil` if your `drafts` table does not have an `object_changes` text
305
+ # column.
272
306
  draft.changeset
273
307
 
274
308
  # Returns whether or not this is a `create` event.
@@ -280,68 +314,73 @@ draft.update?
280
314
  # Returns whether or not this is a `destroy` event.
281
315
  draft.destroy?
282
316
 
283
- # Publishes this draft's associated `item`, publishes its `item`'s dependencies, and destroys itself.
284
- # - For `create` drafts, adds a value for the `published_at` timestamp on the item and destroys the draft.
285
- # - For `update` drafts, applies the drafted changes to the item and destroys the draft.
317
+ # Publishes this draft's associated `item`, publishes its `item`'s dependencies,
318
+ # and destroys itself.
319
+ # - For `create` drafts, adds a value for the `published_at` timestamp on the
320
+ # item and destroys the draft.
321
+ # - For `update` drafts, applies the drafted changes to the item and destroys
322
+ # the draft.
286
323
  # - For `destroy` drafts, destroys the item and the draft.
287
324
  draft.publish!
288
325
 
289
- # Reverts this draft's associated `item` to its previous state, reverts its `item`'s dependencies, and destroys itself.
326
+ # Reverts this draft's associated `item` to its previous state, reverts its
327
+ # `item`'s dependencies, and destroys itself.
290
328
  # - For `create` drafts, destroys the draft and the item.
291
329
  # - For `update` drafts, destroys the draft only.
292
- # - For `destroy` drafts, destroys the draft and undoes the `trashed_at` timestamp on the item. If a draft was drafted
293
- # for destroy, restores the draft.
330
+ # - For `destroy` drafts, destroys the draft and undoes the `trashed_at`
331
+ # timestamp on the item. If a draft was drafted for destroy, restores the
332
+ # draft.
294
333
  draft.revert!
295
334
 
296
- # Returns related draft dependencies that would be along for the ride for a `publish!` action.
335
+ # Returns related draft dependencies that would be along for the ride for a
336
+ # `publish!` action.
297
337
  draft.draft_publication_dependencies
298
338
 
299
- # Returns related draft dependencies that would be along for the ride for a `revert!` action.
339
+ # Returns related draft dependencies that would be along for the ride for a
340
+ # `revert!` action.
300
341
  draft.draft_reversion_dependencies
301
342
  ```
302
343
 
303
344
  ### Callbacks
304
345
 
305
- Draftsman supports callbacks for draft creation, update, and destroy. These callbacks can be defined in any model
306
- that `has_drafts`.
346
+ Draftsman supports callbacks for draft saves and destroys. These callbacks can
347
+ be defined in any model that `has_drafts`.
307
348
 
308
- Draft callbacks work similarly to ActiveRecord callbacks; pass any functions that you would like called
309
- before/around/after a draft persistence method.
349
+ Draft callbacks work similarly to ActiveRecord callbacks; pass any functions
350
+ that you would like called before/around/after a draft persistence method.
310
351
 
311
352
  Available callbacks:
312
353
  ```ruby
313
- before_draft_creation # called before draft is created
314
- around_draft_creation # called function must yield to `draft_creation`
315
- after_draft_creation # called after draft is created
316
-
317
- before_draft_update # called before draft is updated
318
- around_draft_update # called function must yield to `draft_update`
319
- after_draft_update # called after draft is updated
354
+ before_save_draft # called before draft is saved
355
+ around_save_draft # called function must yield to `save_draft`
356
+ after_draft_save # called after draft is saved
320
357
 
321
- before_draft_destruction # called before draft is destroyed
358
+ before_draft_destruction # called before item is destroyed as a draft
322
359
  around_draft_destruction # called function must yield to `draft_destruction`
323
- after_draft_destruction # called after draft is destroyed
360
+ after_draft_destruction # called after item is destroyed as a draft
324
361
  ```
325
362
 
326
363
  Note that callbacks must be defined after your call to `has_drafts`.
327
364
 
328
365
  ## Basic Usage
329
366
 
330
- A basic `widgets` admin controller in Rails that saves all of the user's actions as drafts would look something like
331
- this. It also presents all data in its drafted form, if a draft exists.
367
+ A basic `widgets` admin controller in Rails that saves all of the user's actions
368
+ as drafts would look something like this. It also presents all data in its
369
+ drafted form, if a draft exists.
332
370
 
333
371
  ```ruby
334
372
  class Admin::WidgetsController < Admin::BaseController
335
- before_action :find_widget, :only => [:show, :edit, :update, :destroy]
336
- before_action :reify_widget, :only => [:show, :edit]
373
+ before_action :find_widget, only: [:show, :edit, :update, :destroy]
374
+ before_action :reify_widget, only: [:show, :edit]
337
375
 
338
376
  def index
339
377
  # The `live` scope gives us widgets that aren't in the trash.
340
- # It's also strongly recommended that you eagerly-load the `draft` association via `includes` so you don't keep
341
- # hitting your database for each draft.
378
+ # It's also strongly recommended that you eagerly-load the `draft`
379
+ # association via `includes` so you don't keep hitting your database for
380
+ # each draft.
342
381
  @widgets = Widget.live.includes(:draft).order(:title)
343
382
 
344
- # Load drafted versions of each widget
383
+ # Load drafted versions of each widget.
345
384
  @widgets.map! { |widget| widget.draft.reify if widget.draft? }
346
385
  end
347
386
 
@@ -355,12 +394,12 @@ class Admin::WidgetsController < Admin::BaseController
355
394
  def create
356
395
  @widget = Widget.new(widget_params)
357
396
 
358
- # Instead of calling `save`, you call `draft_creation` to save it as a draft
359
- if @widget.draft_creation
360
- flash[:success] = 'A draft of the new widget was saved successfully.'
361
- redirect_to admin_widgets_path
397
+ # Instead of calling `save`, you call `save_draft` to save it as a draft.
398
+ if @widget.save_draft
399
+ flash[:success] = 'Draft of widget saved successfully.'
400
+ redirect_to [:admin, @widget]
362
401
  else
363
- flash[:error] = 'There was an error creating the widget. Please review the errors below and try again.'
402
+ flash[:error] = 'There was an error saving the draft.'
364
403
  render :new
365
404
  end
366
405
  end
@@ -371,12 +410,12 @@ class Admin::WidgetsController < Admin::BaseController
371
410
  def update
372
411
  @widget.attributes = widget_params
373
412
 
374
- # Instead of calling `update_attributes`, you call `draft_update` to save it as a draft
375
- if @widget.draft_update
376
- flash[:success] = 'A draft of the widget update was saved successfully.'
377
- redirect_to admin_widgets_path
413
+ # Instead of calling `update`, you call `save_draft` to save it as a draft.
414
+ if @widget.save_draft
415
+ flash[:success] = 'Draft of widget saved successfully.'
416
+ redirect_to [:admin, @widget]
378
417
  else
379
- flash[:error] = 'There was an error updating the widget. Please review the errors below and try again.'
418
+ flash[:error] = 'There was an error saving the draft.'
380
419
  render :edit
381
420
  end
382
421
  end
@@ -390,31 +429,33 @@ class Admin::WidgetsController < Admin::BaseController
390
429
 
391
430
  private
392
431
 
393
- # Finds non-trashed widget by `params[:id]`
432
+ # Finds non-trashed widget by `params[:id]`.
394
433
  def find_widget
395
434
  @widget = Widget.live.find(params[:id])
396
435
  end
397
436
 
398
- # If the widget has a draft, load that version of it
437
+ # If the widget has a draft, load that version of it.
399
438
  def reify_widget
400
439
  @widget = @widget.draft.reify if @widget.draft?
401
440
  end
402
441
 
403
- # Strong parameters in Rails 4+
442
+ # Strong parameters for widget form.
404
443
  def widget_params
405
444
  params.require(:widget).permit(:title)
406
445
  end
407
446
  end
408
447
  ```
409
448
 
410
- And "public" controllers (let's say read-only for this simple example) would ignore drafts entirely via the `published`
411
- scope. This also allows items to be "trashed" for admins but still accessible to the public until that deletion is
449
+ And "public" controllers (let's say read-only for this simple example) would
450
+ ignore drafts entirely via the `published` scope. This also allows items to be
451
+ "trashed" for admins but still accessible to the public until that deletion is
412
452
  committed.
413
453
 
414
454
  ```ruby
415
455
  class WidgetsController < ApplicationController
416
456
  def index
417
- # The `published` scope gives us widgets that have been committed to be viewed by non-admin users.
457
+ # The `published` scope gives us widgets that have been committed to be
458
+ # viewed by non-admin users.
418
459
  @widgets = Widget.published.order(:title)
419
460
  end
420
461
 
@@ -424,18 +465,21 @@ class WidgetsController < ApplicationController
424
465
  end
425
466
  ```
426
467
 
427
- Obviously, you can use the scopes that Draftsman provides however you would like in any case.
468
+ Obviously, you can use the scopes that Draftsman provides however you would like
469
+ in any case.
428
470
 
429
- Lastly, a `drafts` controller could be provided for admin users to see all drafts, no matter the type of record (thanks
430
- to ActiveRecord's polymorphic associations). From there, they could choose to revert or publish any draft listed, or any
431
- other workflow action that you would like for your application to provide for drafts.
471
+ Lastly, a `drafts` controller could be provided for admin users to see all
472
+ drafts, no matter the type of record (thanks to ActiveRecord's polymorphic
473
+ associations). From there, they could choose to revert or publish any draft
474
+ listed, or any other workflow action that you would like for your application to
475
+ provide for drafts.
432
476
 
433
477
  ```ruby
434
478
  class Admin::DraftsController < Admin::BaseController
435
- before_action :find_draft, :only => [:show, :update, :destroy]
479
+ before_action :find_draft, only: [:show, :update, :destroy]
436
480
 
437
481
  def index
438
- @drafts = Draftsman::Draft.includes(:item).order('updated_at DESC')
482
+ @drafts = Draftsman::Draft.includes(:item).order(updated_at: :desc)
439
483
  end
440
484
 
441
485
  def show
@@ -443,19 +487,22 @@ class Admin::DraftsController < Admin::BaseController
443
487
 
444
488
  # Post draft ID here to publish it
445
489
  def update
446
- # Call `draft_publication_dependencies` to check if any other drafted records should be published along with this
447
- # `@draft`.
490
+ # Call `draft_publication_dependencies` to check if any other drafted
491
+ # records should be published along with this `@draft`.
448
492
  @dependencies = @draft.draft_publication_dependencies
449
493
 
450
- # If you would like to warn the user about dependent drafts that would need to be published along with this one, you
451
- # would implement an `app/views/drafts/update.html.erb` view template. In that view template, you could list the
452
- # `@dependencies` and show a button posting back to this action with a name of `commit_publication`. (The button's
453
- # being clicked indicates to your application that the user accepts that the dependencies should be published along
454
- # with the `@draft`, thus avoiding orphaned records).
494
+ # If you would like to warn the user about dependent drafts that would need
495
+ # to be published along with this one, you would implement an
496
+ # `app/views/drafts/update.html.erb` view template. In that view template,
497
+ # you could list the `@dependencies` and show a button posting back to this
498
+ # action with a name of `commit_publication`. (The button's being clicked
499
+ # indicates to your application that the user accepts that the dependencies
500
+ # should be published along with the `@draft`, thus avoiding orphaned
501
+ # records).
455
502
  if @dependencies.empty? || params[:commit_publication]
456
503
  @draft.publish!
457
504
  flash[:success] = 'The draft was published successfully.'
458
- redirect_to admin_drafts_path
505
+ redirect_to [:admin, :drafts]
459
506
  else
460
507
  # Renders `app/views/drafts/update.html.erb`
461
508
  end
@@ -463,19 +510,22 @@ class Admin::DraftsController < Admin::BaseController
463
510
 
464
511
  # Post draft ID here to revert it
465
512
  def destroy
466
- # Call `draft_reversion_dependencies` to check if any other drafted records should be reverted along with this
467
- # `@draft`.
513
+ # Call `draft_reversion_dependencies` to check if any other drafted records
514
+ # should be reverted along with this `@draft`.
468
515
  @dependencies = @draft.draft_reversion_dependencies
469
516
 
470
- # If you would like to warn the user about dependent drafts that would need to be reverted along with this one, you
471
- # would implement an `app/views/drafts/destroy.html.erb` view template. In that view template, you could list the
472
- # `@dependencies` and show a button posting back to this action with a name of `commit_reversion`. (The button's
473
- # being clicked indicates to your application that the user accepts that the dependencies should be reverted along
474
- # with the `@draft`, thus avoiding orphaned records).
517
+ # If you would like to warn the user about dependent drafts that would need
518
+ # to be reverted along with this one, you would implement an
519
+ # `app/views/drafts/destroy.html.erb` view template. In that view template,
520
+ # you could list the `@dependencies` and show a button posting back to this
521
+ # action with a name of `commit_reversion`. (The button's being clicked
522
+ # indicates to your application that the user accepts that the dependencies
523
+ # should be reverted along with the `@draft`, thus avoiding orphaned
524
+ # records).
475
525
  if @dependencies.empty? || params[:commit_reversion]
476
526
  @draft.revert!
477
527
  flash[:success] = 'The draft was reverted successfully.'
478
- redirect_to admin_drafts_path
528
+ redirect_to [:admin, :drafts]
479
529
  else
480
530
  # Renders `app/views/drafts/destroy.html.erb`
481
531
  end
@@ -497,8 +547,8 @@ If you would like your `Widget` to have callbacks, it might look something like
497
547
  class Widget < ActiveRecord::Base
498
548
  has_drafts
499
549
 
500
- before_draft_creation :say_hi
501
- around_draft_update :surround_update
550
+ before_save_draft :say_hi
551
+ around_save_draft :surround_update
502
552
 
503
553
  private
504
554
 
@@ -507,9 +557,13 @@ private
507
557
  end
508
558
 
509
559
  def surround_update
510
- # do something before update
511
- yield
512
- # do something after update
560
+ if self.persisted?
561
+ # do something before update
562
+ yield
563
+ # do something after update
564
+ else
565
+ yield
566
+ end
513
567
  end
514
568
  end
515
569
  ```
@@ -517,23 +571,29 @@ end
517
571
 
518
572
  ## Differences from PaperTrail
519
573
 
520
- If you are familiar with the PaperTrail gem, some parts of the Draftsman gem will look very familiar.
574
+ If you are familiar with the PaperTrail gem, some parts of the Draftsman gem
575
+ will look very familiar.
521
576
 
522
577
  However, there are some differences:
523
578
 
524
- * PaperTrail hooks into ActiveRecord callbacks so that versions can be saved automatically with your normal CRUD
525
- operations (`save`, `create`, `update`, `destroy`, etc.). Draftsman requires that you explicitly call its own
526
- CRUD methods in order to save a draft (`draft_creation`, `draft_update`, and `draft_destruction`).
579
+ * PaperTrail hooks into ActiveRecord callbacks so that versions can be saved
580
+ automatically with your normal CRUD operations (`#save`, `#create`,
581
+ `#update`, `#destroy`, etc.). Draftsman requires that you explicitly call its
582
+ own CRUD methods in order to save a draft (`#save_draft` and
583
+ `draft_destruction`).
527
584
 
528
- * PaperTrail's `Version#object` column looks "backwards" and records the object's state _before_ the changes occurred.
529
- Because drafts record changes as they will look in the future, they must work differently. Draftsman's `Draft#object`
530
- records the object's state _after_ changes are applied to the master object. *But* `destroy` drafts record the object
531
- as it was _before_ it was destroyed (in case you want the option of reverting the destroy later and restoring the
532
- drafted item back to its original state).
585
+ * PaperTrail's `Version#object` column looks "backward" and records the
586
+ object's state _before_ the changes occurred. Because drafts record changes
587
+ as they will look in the future, they must work differently. Draftsman's
588
+ `Draft#object` records the object's state _after_ changes are applied to the
589
+ master object. *But* `destroy` drafts record the object as it was _before_ it
590
+ was destroyed (in case you want the option of reverting the destroy later and
591
+ restoring the drafted item back to its original state).
533
592
 
534
593
  ## Semantic Versioning
535
594
 
536
- Like many Ruby gems, Draftsman honors the concepts behind [semantic versioning][10]:
595
+ Like many Ruby gems, Draftsman honors the concepts behind
596
+ [semantic versioning][10]:
537
597
 
538
598
  > Given a version number MAJOR.MINOR.PATCH, increment the:
539
599
  >
@@ -543,35 +603,38 @@ Like many Ruby gems, Draftsman honors the concepts behind [semantic versioning][
543
603
 
544
604
  ## Contributing
545
605
 
546
- If you feel like you can add something useful to Draftsman, then don't hesitate to contribute! To make sure your
547
- fix/feature has a high chance of being included, please do the following:
606
+ If you feel like you can add something useful to Draftsman, then don't hesitate
607
+ to contribute! To make sure your fix/feature has a high chance of being
608
+ included, please do the following:
548
609
 
549
610
  1. Fork the repo.
550
611
 
551
612
  2. Run `bundle install`.
552
613
 
553
- 3. `cd spec/dummy` and run `RAILS_ENV=test rake db:migrate` to apply test database migrations.
614
+ 3. `cd spec/dummy` and run `RAILS_ENV=test rake db:migrate` to apply test
615
+ database migrations.
554
616
 
555
- 4. Add at least one test for your change. Only refactoring and documentation changes require no new tests. If you are
556
- adding functionality or fixing a bug, you need a test!
617
+ 4. Add at least one test for your change. Only refactoring and documentation
618
+ changes require no new tests. If you are adding functionality or fixing a
619
+ bug, you need a test!
557
620
 
558
- 5. Make all tests pass by running `rspec spec`.
621
+ 5. Make all tests pass by running `rake`.
559
622
 
560
623
  6. Push to your fork and submit a pull request.
561
624
 
562
- I can't guarantee that I will accept the change, but if I don't, I will be sure to let you know why.
625
+ I can't guarantee that I will accept the change, but if I don't, I will be sure
626
+ to let you know why.
563
627
 
564
- Here are some things that will increase the chance that your pull request is accepted, taken straight from the Ruby on
565
- Rails guide:
628
+ Here are some things that will increase the chance that your pull request is
629
+ accepted, taken straight from the Ruby on Rails guide:
566
630
 
567
- * Use Rails idioms
568
- * Because this gem is currently designed to run with Rails 3, use Ruby 1.8-supported syntax (e,g.,
569
- `item.where(:foo => :bar)`, instead of the newer Ruby 1.9-style `item.where(foo: :bar)`)
570
- * Include tests that fail without your code, and pass with it
571
- * Update the documentation, guides, or whatever is affected by your contribution
631
+ - Use Rails idioms
632
+ - Include tests that fail without your code, and pass with it
633
+ - Update the documentation, guides, or whatever is affected by your
634
+ contribution
572
635
 
573
- This gem is a work in progress. I am adding specs as I need features in my application. Please add missing ones as you
574
- work on features or find bugs!
636
+ This gem is a work in progress. I am adding specs as I need features in my
637
+ application. Please add missing ones as you work on features or find bugs!
575
638
 
576
639
  ## License
577
640
 
@@ -585,7 +648,7 @@ Draftsman is released under the [MIT License][9].
585
648
  [4]: http://railscasts.com/episodes/416-form-objects
586
649
  [5]: http://www.sinatrarb.com/
587
650
  [6]: https://github.com/janko-m/sinatra-activerecord
588
- [7]: https://raw.github.com/live-editor/draftsman/master/lib/generators/draftsman/templates/create_drafts.rb
651
+ [7]: https://raw.github.com/liveeditor/draftsman/master/lib/generators/draftsman/templates/create_drafts.rb
589
652
  [8]: http://www.sinatrarb.com/intro.html#Modular%20vs.%20Classic%20Style
590
653
  [9]: http://www.opensource.org/licenses/MIT
591
654
  [10]: http://semver.org/