draftsman 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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/