draftsman 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +1 -1
- data/README.md +7 -5
- data/lib/draftsman/draft.rb +24 -23
- data/lib/draftsman/model.rb +32 -21
- data/lib/draftsman/version.rb +1 -1
- data/lib/generators/draftsman/install_generator.rb +18 -4
- data/lib/generators/draftsman/templates/add_object_changes_column_to_drafts_json.rb +9 -0
- data/lib/generators/draftsman/templates/config/initializers/draftsman.rb +5 -1
- data/lib/generators/draftsman/templates/create_drafts_json.rb +22 -0
- data/spec/models/draft_spec.rb +267 -259
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4594a89239d63ca33dfc4b008728952b04ff3d77
|
4
|
+
data.tar.gz: a41330672fbc6204b0a9b459db106b05cddb3675
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4a5197c59c7c471a2abeaf558274b19fa5938eba14b319ba09fe571bca17b7df2363122c87ff00dd9f4bbe34e83177056e904a535add241b7c702e2b1ce45c00
|
7
|
+
data.tar.gz: 31ad8696e86643c2c230fd40df6c66510360db0c8c98728fb89c27902738cde76dc5575ee7b8baf09c5c1f2b8574c923a1279c25fa8d6fac12c85408c453ad54
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 0.3.0 - July 29, 2014
|
4
|
+
|
5
|
+
- Commit [1e2a59f678](https://github.com/live-editor/draftsman/commit/1e2a59f678cc4d88222dfc1976d564b5649cd329) - Add support for PostgreSQL JSON data type for `object`, `object_changes`, and `previous_draft` columns.
|
6
|
+
|
3
7
|
## v0.2.1 - June 28, 2014
|
4
8
|
|
5
9
|
- Commit [dbc6c83abb](https://github.com/live-editor/draftsman/commit/dbc6c83abbea5211f67ad883f4a2d18a9f5ac181) - Reifying a record that was drafted for destruction uses data from a drafted update before that if that's what happened.
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Draftsman v0.
|
1
|
+
# Draftsman v0.3.0 (alpha)
|
2
2
|
|
3
3
|
Draftsman is a Ruby gem that lets you create draft versions of your database records. If you're developing a system in
|
4
4
|
need of simple drafts or a publishing approval queue, then Draftsman just might be what you need.
|
@@ -50,7 +50,7 @@ Works well with Rails, Sinatra, or any other application that depends on ActiveR
|
|
50
50
|
Add Draftsman to your `Gemfile`.
|
51
51
|
|
52
52
|
```ruby
|
53
|
-
gem 'draftsman', '0.
|
53
|
+
gem 'draftsman', '0.3.0'
|
54
54
|
```
|
55
55
|
|
56
56
|
Or if you want to grab the latest from `master`:
|
@@ -61,14 +61,16 @@ gem 'draftsman', :github => 'live-editor/draftsman'
|
|
61
61
|
|
62
62
|
Generate a migration which will add a `drafts` table to your database.
|
63
63
|
|
64
|
-
rails g draftsman:install
|
64
|
+
$ rails g draftsman:install
|
65
65
|
|
66
|
-
You can pass zero
|
66
|
+
You can pass zero or any combination of these options to the generator:
|
67
67
|
|
68
68
|
$ rails g draftsman:install --skip-initializer # Skip generation of the boilerplate initializer at
|
69
69
|
# `config/initializers/draftsman.rb`.
|
70
70
|
|
71
|
-
$ rails g draftsman:install --with-changes # Store changeset (diff) with each draft
|
71
|
+
$ rails g draftsman:install --with-changes # Store changeset (diff) with each draft.
|
72
|
+
|
73
|
+
$ rails g draftsman:install --with-pg-json # Use PostgreSQL JSON data type for serialized data.
|
72
74
|
|
73
75
|
Run the migration(s).
|
74
76
|
|
data/lib/draftsman/draft.rb
CHANGED
@@ -22,6 +22,16 @@ class Draftsman::Draft < ActiveRecord::Base
|
|
22
22
|
where :event => 'destroy'
|
23
23
|
end
|
24
24
|
|
25
|
+
# Returns whether the `object` column is using the `json` type supported by PostgreSQL.
|
26
|
+
def self.object_col_is_json?
|
27
|
+
@object_col_is_json ||= columns_hash['object'].type == :json
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns whether the `object_changes` column is using the `json` type supported by PostgreSQL.
|
31
|
+
def self.object_changes_col_is_json?
|
32
|
+
@object_changes_col_is_json ||= columns_hash['object_changes'].type == :json
|
33
|
+
end
|
34
|
+
|
25
35
|
def self.updates
|
26
36
|
where :event => 'update'
|
27
37
|
end
|
@@ -31,7 +41,9 @@ class Draftsman::Draft < ActiveRecord::Base
|
|
31
41
|
def changeset
|
32
42
|
return nil unless self.class.column_names.include? 'object_changes'
|
33
43
|
|
34
|
-
|
44
|
+
_changes = self.class.object_changes_col_is_json? ? self.object_changes : Draftsman.serializer.load(self.object_changes)
|
45
|
+
|
46
|
+
@changeset ||= HashWithIndifferentAccess.new(_changes).tap do |changes|
|
35
47
|
item_type.constantize.unserialize_draft_attribute_changes(changes)
|
36
48
|
end
|
37
49
|
rescue
|
@@ -179,7 +191,7 @@ class Draftsman::Draft < ActiveRecord::Base
|
|
179
191
|
end
|
180
192
|
end
|
181
193
|
|
182
|
-
# Returns instance of item
|
194
|
+
# Returns instance of item converted to its drafted state.
|
183
195
|
#
|
184
196
|
# Example usage:
|
185
197
|
#
|
@@ -194,7 +206,10 @@ class Draftsman::Draft < ActiveRecord::Base
|
|
194
206
|
|
195
207
|
model = item.reload
|
196
208
|
|
197
|
-
Draftsman.serializer.load(
|
209
|
+
attrs = self.class.object_col_is_json? ? self.object : Draftsman.serializer.load(object)
|
210
|
+
model.class.unserialize_attributes_for_draftsman attrs
|
211
|
+
|
212
|
+
attrs.each do |key, value|
|
198
213
|
# Skip counter_cache columns
|
199
214
|
if model.respond_to?("#{key}=") && !key.end_with?('_count')
|
200
215
|
model.send "#{key}=", value
|
@@ -211,9 +226,9 @@ class Draftsman::Draft < ActiveRecord::Base
|
|
211
226
|
|
212
227
|
# Reverts this draft.
|
213
228
|
# - For create drafts, destroys the draft and the item.
|
214
|
-
# - For update drafts,
|
215
|
-
# - For destroy drafts, destroys the draft and undoes the `trashed_at` timestamp on the item. If a draft was
|
216
|
-
# for destroy, restores the draft.
|
229
|
+
# - For update drafts, destroys the draft only.
|
230
|
+
# - For destroy drafts, destroys the draft and undoes the `trashed_at` timestamp on the item. If a previous draft was
|
231
|
+
# drafted for destroy, restores the draft.
|
217
232
|
def revert!
|
218
233
|
ActiveRecord::Base.transaction do
|
219
234
|
case self.event
|
@@ -256,7 +271,9 @@ private
|
|
256
271
|
draft = self.class.new
|
257
272
|
|
258
273
|
without_identity_map do
|
259
|
-
Draftsman.serializer.load(self.previous_draft)
|
274
|
+
attrs = self.class.object_col_is_json? ? self.previous_draft : Draftsman.serializer.load(self.previous_draft)
|
275
|
+
|
276
|
+
attrs.each do |key, value|
|
260
277
|
if key.to_sym != :id && draft.respond_to?("#{key}=")
|
261
278
|
draft.send "#{key}=", value
|
262
279
|
elsif key.to_sym != :id
|
@@ -268,22 +285,6 @@ private
|
|
268
285
|
draft
|
269
286
|
end
|
270
287
|
|
271
|
-
# Saves associated draft dependencies by reflecting `belongs_to` associations and identifying which ones are
|
272
|
-
# draftable.
|
273
|
-
#def save_draft_dependencies
|
274
|
-
# self.item.class.reflect_on_all_associations(:belongs_to).each do |association|
|
275
|
-
# associated_object = self.item.send(association.name)
|
276
|
-
#
|
277
|
-
# if associated_object.present? && associated_object.respond_to?(:draft?)
|
278
|
-
# if associated_object.reload.draft?
|
279
|
-
# Draftsman::DraftDependency.create(:draft_id => self.id, :dependency_id => associated_object.id)
|
280
|
-
# else
|
281
|
-
# Draftsman::DraftDependency.where(:draft_id => self.id, :dependency_id => associated_object.id).delete_all
|
282
|
-
# end
|
283
|
-
# end
|
284
|
-
# end
|
285
|
-
#end
|
286
|
-
|
287
288
|
def without_identity_map(&block)
|
288
289
|
if defined?(ActiveRecord::IdentityMap) && ActiveRecord::IdentityMap.respond_to?(:without)
|
289
290
|
ActiveRecord::IdentityMap.without &block
|
data/lib/draftsman/model.rb
CHANGED
@@ -110,6 +110,11 @@ module Draftsman
|
|
110
110
|
end)
|
111
111
|
end
|
112
112
|
|
113
|
+
# Returns draft class.
|
114
|
+
def draft_class
|
115
|
+
@draft_class ||= draft_class_name.constantize
|
116
|
+
end
|
117
|
+
|
113
118
|
# Returns whether or not `has_drafts` has been called on this model.
|
114
119
|
def draftable?
|
115
120
|
method_defined?(:draftsman_options)
|
@@ -122,6 +127,9 @@ module Draftsman
|
|
122
127
|
|
123
128
|
# Serializes attribute changes for `Draft#object_changes` attribute.
|
124
129
|
def serialize_draft_attribute_changes(changes)
|
130
|
+
# Don't serialize values before inserting into columns of type `JSON` on PostgreSQL databases.
|
131
|
+
return changes if self.draft_class.object_changes_col_is_json?
|
132
|
+
|
125
133
|
serialized_attributes.each do |key, coder|
|
126
134
|
if changes.key?(key)
|
127
135
|
coder = Draftsman::Serializers::Yaml unless coder.respond_to?(:dump) # Fall back to YAML if `coder` has no `dump` method
|
@@ -133,6 +141,9 @@ module Draftsman
|
|
133
141
|
|
134
142
|
# Used for `Draft#object` attribute
|
135
143
|
def serialize_attributes_for_draftsman(attributes)
|
144
|
+
# Don't serialize values before inserting into columns of type `JSON` on PostgreSQL databases.
|
145
|
+
return attributes if self.draft_class.object_col_is_json?
|
146
|
+
|
136
147
|
serialized_attributes.each do |key, coder|
|
137
148
|
if attributes.key?(key)
|
138
149
|
coder = Draftsman::Serializers::Yaml unless coder.respond_to?(:dump) # Fall back to YAML if `coder` has no `dump` method
|
@@ -148,6 +159,9 @@ module Draftsman
|
|
148
159
|
|
149
160
|
# Unserializes attribute changes for `Draft#object_changes` attribute.
|
150
161
|
def unserialize_draft_attribute_changes(changes)
|
162
|
+
# Don't serialize values before inserting into columns of type `JSON` on PostgreSQL databases.
|
163
|
+
return changes if self.draft_class.object_changes_col_is_json?
|
164
|
+
|
151
165
|
serialized_attributes.each do |key, coder|
|
152
166
|
if changes.key?(key)
|
153
167
|
coder = Draftsman::Serializers::Yaml unless coder.respond_to?(:dump)
|
@@ -159,6 +173,9 @@ module Draftsman
|
|
159
173
|
|
160
174
|
# Used for `Draft#object` attribute
|
161
175
|
def unserialize_attributes_for_draftsman(attributes)
|
176
|
+
# Don't serialize values before inserting into columns of type `JSON` on PostgreSQL databases.
|
177
|
+
return attributes if self.draft_class.object_col_is_json?
|
178
|
+
|
162
179
|
serialized_attributes.each do |key, coder|
|
163
180
|
if attributes.key?(key)
|
164
181
|
coder = Draftsman::Serializers::Yaml unless coder.respond_to?(:dump)
|
@@ -190,9 +207,9 @@ module Draftsman
|
|
190
207
|
:item => self,
|
191
208
|
:event => 'create',
|
192
209
|
:whodunnit => Draftsman.whodunnit,
|
193
|
-
:object =>
|
210
|
+
:object => object_attrs_for_draft_record
|
194
211
|
}
|
195
|
-
data[:object_changes] =
|
212
|
+
data[:object_changes] = changes_for_draftsman(previous_changes: true) if track_object_changes_for_draft?
|
196
213
|
data = merge_metadata_for_draft(data)
|
197
214
|
|
198
215
|
send "build_#{self.class.draft_association_name}", data
|
@@ -214,7 +231,7 @@ module Draftsman
|
|
214
231
|
:item => self,
|
215
232
|
:event => 'destroy',
|
216
233
|
:whodunnit => Draftsman.whodunnit,
|
217
|
-
:object =>
|
234
|
+
:object => object_attrs_for_draft_record
|
218
235
|
}
|
219
236
|
|
220
237
|
# Stash previous draft in case it needs to be reverted later
|
@@ -266,11 +283,11 @@ module Draftsman
|
|
266
283
|
data = {
|
267
284
|
:item => self,
|
268
285
|
:whodunnit => Draftsman.whodunnit,
|
269
|
-
:object =>
|
286
|
+
:object => object_attrs_for_draft_record
|
270
287
|
}
|
271
288
|
|
272
289
|
if track_object_changes_for_draft?
|
273
|
-
data[:object_changes] =
|
290
|
+
data[:object_changes] = changes_for_draftsman(changed_from: self.send(self.class.draft_association_name).changeset)
|
274
291
|
end
|
275
292
|
|
276
293
|
data = merge_metadata_for_draft(data)
|
@@ -288,21 +305,18 @@ module Draftsman
|
|
288
305
|
data = {
|
289
306
|
:item => self,
|
290
307
|
:whodunnit => Draftsman.whodunnit,
|
291
|
-
:object =>
|
308
|
+
:object => object_attrs_for_draft_record
|
292
309
|
}
|
293
310
|
data = merge_metadata_for_draft(data)
|
294
311
|
|
295
312
|
# If there's already a draft, update it
|
296
313
|
if send(self.class.draft_association_name).present?
|
297
|
-
if track_object_changes_for_draft?
|
298
|
-
data[:object_changes] = Draftsman.serializer.dump(changes_for_draftsman)
|
299
|
-
end
|
300
|
-
|
314
|
+
data[:object_changes] = changes_for_draftsman if track_object_changes_for_draft?
|
301
315
|
send(self.class.draft_association_name).update_attributes data
|
302
316
|
# If there's not draft, create an update draft
|
303
317
|
else
|
304
318
|
data[:event] = 'update'
|
305
|
-
data[:object_changes] =
|
319
|
+
data[:object_changes] = changes_for_draftsman if track_object_changes_for_draft?
|
306
320
|
send "build_#{self.class.draft_association_name}", data
|
307
321
|
|
308
322
|
if send(self.class.draft_association_name).save
|
@@ -317,9 +331,9 @@ module Draftsman
|
|
317
331
|
data = {
|
318
332
|
:item => self,
|
319
333
|
:whodunnit => Draftsman.whodunnit,
|
320
|
-
:object =>
|
334
|
+
:object => object_attrs_for_draft_record
|
321
335
|
}
|
322
|
-
data[:object_changes] =
|
336
|
+
data[:object_changes] = changes_for_draftsman(changed_from: @object.draft.changeset) if track_object_changes_for_draft?
|
323
337
|
data = merge_metadata_for_draft(data)
|
324
338
|
|
325
339
|
send(self.class.draft_association_name).update_attributes data
|
@@ -333,14 +347,14 @@ module Draftsman
|
|
333
347
|
end
|
334
348
|
|
335
349
|
# Returns serialized object representing this drafted item.
|
336
|
-
def
|
350
|
+
def object_attrs_for_draft_record(object = nil)
|
337
351
|
object ||= self
|
338
352
|
|
339
353
|
_attrs = object.attributes.except(*self.class.draftsman_options[:skip]).tap do |attributes|
|
340
354
|
self.class.serialize_attributes_for_draftsman attributes
|
341
355
|
end
|
342
356
|
|
343
|
-
Draftsman.serializer.dump(_attrs)
|
357
|
+
self.class.draft_class.object_col_is_json? ? _attrs : Draftsman.serializer.dump(_attrs)
|
344
358
|
end
|
345
359
|
|
346
360
|
# Returns whether or not this item has been published at any point in its lifecycle.
|
@@ -395,12 +409,9 @@ module Draftsman
|
|
395
409
|
|
396
410
|
# We need to merge any previous changes so they are not lost on further updates before committing or
|
397
411
|
# reverting
|
398
|
-
options[:changed_from].merge new_changes
|
399
|
-
end
|
412
|
+
my_changes = options[:changed_from].merge new_changes
|
400
413
|
|
401
|
-
|
402
|
-
def draft_class
|
403
|
-
self.draft_class_name.constantize
|
414
|
+
self.class.draft_class.object_changes_col_is_json? ? my_changes : Draftsman.serializer.dump(my_changes)
|
404
415
|
end
|
405
416
|
|
406
417
|
# Merges model-level metadata from `meta` and `controller_info` into draft object.
|
@@ -450,7 +461,7 @@ module Draftsman
|
|
450
461
|
|
451
462
|
# Returns whether or not the draft class includes an `object_changes` attribute.
|
452
463
|
def track_object_changes_for_draft?
|
453
|
-
draft_class.column_names.include? 'object_changes'
|
464
|
+
self.class.draft_class.column_names.include? 'object_changes'
|
454
465
|
end
|
455
466
|
|
456
467
|
# Sets `trashed_at` attribute to now and saves to the database immediately.
|
data/lib/draftsman/version.rb
CHANGED
@@ -6,14 +6,28 @@ module Draftsman
|
|
6
6
|
class InstallGenerator < ::Rails::Generators::Base
|
7
7
|
include ::Rails::Generators::Migration
|
8
8
|
|
9
|
-
desc '
|
9
|
+
desc 'Creates config initializer and generates (but does not run) a migration to add a drafts table.'
|
10
10
|
source_root File.expand_path('../templates', __FILE__)
|
11
11
|
class_option :skip_initializer, :type => :boolean, :default => false, :desc => 'Skip generation of the boilerplate initializer at `config/initializers/draftsman.rb`.'
|
12
|
-
class_option :with_changes, :type => :boolean, :default => false, :desc => 'Store changeset (diff) with each draft'
|
12
|
+
class_option :with_changes, :type => :boolean, :default => false, :desc => 'Store changeset (diff) with each draft.'
|
13
|
+
class_option :with_pg_json, :type => :boolean, :default => false, :desc => 'Use PostgreSQL JSON data type for serialized data.'
|
13
14
|
|
14
15
|
def create_migration_file
|
15
|
-
|
16
|
-
|
16
|
+
if options.with_pg_json?
|
17
|
+
migration_template 'create_drafts_json.rb', 'db/migrate/create_drafts.rb'
|
18
|
+
|
19
|
+
if options.with_changes?
|
20
|
+
migration_template 'add_object_changes_column_to_drafts_json.rb',
|
21
|
+
'db/migrate/add_object_changes_column_to_drafts.rb'
|
22
|
+
end
|
23
|
+
else
|
24
|
+
migration_template 'create_drafts.rb', 'db/migrate/create_drafts.rb'
|
25
|
+
|
26
|
+
if options.with_changes?
|
27
|
+
migration_template 'add_object_changes_column_to_drafts.rb',
|
28
|
+
'db/migrate/add_object_changes_column_to_drafts.rb'
|
29
|
+
end
|
30
|
+
end
|
17
31
|
end
|
18
32
|
|
19
33
|
def self.next_migration_number(dirname)
|
@@ -1,10 +1,14 @@
|
|
1
1
|
# Override global `draft` class. For example, perhaps you want your own class at `app/models/draft.rb` that adds
|
2
|
-
# extra attributes, validations, associations, etc. Be sure that this new model class extends
|
2
|
+
# extra attributes, validations, associations, methods, etc. Be sure that this new model class extends
|
3
|
+
# `Draftsman::Draft`.
|
3
4
|
# Draftsman.draft_class_name = 'Draftsman::Draft'
|
4
5
|
|
5
6
|
# Serializer for `object`, `object_changes`, and `previous_draft` columns. To use the JSON serializer, change to
|
6
7
|
# `Draftsman::Serializers::Json`. You could implement your own serializer if you really wanted to. See files in
|
7
8
|
# `lib/draftsman/serializers`.
|
9
|
+
#
|
10
|
+
# Note: this option is not needed if you're using the PostgreSQL JSON data type for the `object`,
|
11
|
+
# `object_changes`, and `previous_draft` columns.
|
8
12
|
# Draftsman.serializer = Draftsman::Serializers::Json
|
9
13
|
|
10
14
|
# Field which records when a draft was created.
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class CreateDrafts < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :drafts do |t|
|
4
|
+
t.string :item_type, :null => false
|
5
|
+
t.integer :item_id, :null => false
|
6
|
+
t.string :event, :null => false
|
7
|
+
t.string :whodunnit# :null => false
|
8
|
+
t.json :object
|
9
|
+
t.json :previous_draft
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
|
13
|
+
change_table :drafts do |t|
|
14
|
+
t.index :item_type
|
15
|
+
t.index :item_id
|
16
|
+
t.index :event
|
17
|
+
t.index :whodunnit
|
18
|
+
t.index :created_at
|
19
|
+
t.index :updated_at
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/spec/models/draft_spec.rb
CHANGED
@@ -1,34 +1,23 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Draftsman::Draft do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
before { trashable.draft_creation }
|
10
|
-
its(:event) { should eql 'create' }
|
11
|
-
its(:create?) { should be_true }
|
12
|
-
its(:update?) { should be_false }
|
13
|
-
its(:destroy?) { should be_false }
|
14
|
-
its(:object) { should be_present }
|
15
|
-
its(:changeset) { should include :id }
|
16
|
-
its(:changeset) { should include :name }
|
17
|
-
its(:changeset) { should_not include :title }
|
18
|
-
its(:changeset) { should include :created_at }
|
19
|
-
its(:changeset) { should include :updated_at }
|
20
|
-
its(:previous_draft) { should be_nil }
|
21
|
-
|
22
|
-
context 'updated create' do
|
23
|
-
before do
|
24
|
-
trashable.name = 'Sam'
|
25
|
-
trashable.draft_update
|
26
|
-
end
|
4
|
+
describe 'class methods' do
|
5
|
+
subject { Draftsman::Draft }
|
6
|
+
its(:object_col_is_json?) { should be_false }
|
7
|
+
its(:object_changes_col_is_json?) { should be_false }
|
8
|
+
end
|
27
9
|
|
28
|
-
|
29
|
-
|
30
|
-
|
10
|
+
describe 'instance methods' do
|
11
|
+
let(:trashable) { Trashable.new :name => 'Bob' }
|
12
|
+
subject { trashable.draft }
|
13
|
+
|
14
|
+
describe :event, :create?, :update?, :destroy?, :object, :changeset do
|
15
|
+
context 'with `create` draft' do
|
16
|
+
before { trashable.draft_creation }
|
31
17
|
its(:event) { should eql 'create' }
|
18
|
+
its(:create?) { should be_true }
|
19
|
+
its(:update?) { should be_false }
|
20
|
+
its(:destroy?) { should be_false }
|
32
21
|
its(:object) { should be_present }
|
33
22
|
its(:changeset) { should include :id }
|
34
23
|
its(:changeset) { should include :name }
|
@@ -36,32 +25,32 @@ describe Draftsman::Draft do
|
|
36
25
|
its(:changeset) { should include :created_at }
|
37
26
|
its(:changeset) { should include :updated_at }
|
38
27
|
its(:previous_draft) { should be_nil }
|
39
|
-
end
|
40
|
-
end
|
41
28
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
29
|
+
context 'updated create' do
|
30
|
+
before do
|
31
|
+
trashable.name = 'Sam'
|
32
|
+
trashable.draft_update
|
33
|
+
end
|
34
|
+
|
35
|
+
it { should be_create }
|
36
|
+
it { should_not be_update }
|
37
|
+
it { should_not be_destroy }
|
38
|
+
its(:event) { should eql 'create' }
|
39
|
+
its(:object) { should be_present }
|
40
|
+
its(:changeset) { should include :id }
|
41
|
+
its(:changeset) { should include :name }
|
42
|
+
its(:changeset) { should_not include :title }
|
43
|
+
its(:changeset) { should include :created_at }
|
44
|
+
its(:changeset) { should include :updated_at }
|
45
|
+
its(:previous_draft) { should be_nil }
|
46
|
+
end
|
48
47
|
end
|
49
48
|
|
50
|
-
|
51
|
-
it { should be_update }
|
52
|
-
it { should_not be_destroy }
|
53
|
-
its(:event) { should eql 'update' }
|
54
|
-
its(:object) { should be_present }
|
55
|
-
its(:changeset) { should_not include :id }
|
56
|
-
its(:changeset) { should include :name }
|
57
|
-
its(:changeset) { should include :title }
|
58
|
-
its(:changeset) { should_not include :created_at }
|
59
|
-
its(:changeset) { should_not include :updated_at }
|
60
|
-
its(:previous_draft) { should be_nil }
|
61
|
-
|
62
|
-
context 'updating the update' do
|
49
|
+
context 'with `update` draft' do
|
63
50
|
before do
|
64
|
-
trashable.
|
51
|
+
trashable.save!
|
52
|
+
trashable.name = 'Sam'
|
53
|
+
trashable.title = 'My Title'
|
65
54
|
trashable.draft_update
|
66
55
|
end
|
67
56
|
|
@@ -72,186 +61,180 @@ describe Draftsman::Draft do
|
|
72
61
|
its(:object) { should be_present }
|
73
62
|
its(:changeset) { should_not include :id }
|
74
63
|
its(:changeset) { should include :name }
|
75
|
-
its(:changeset) {
|
64
|
+
its(:changeset) { should include :title }
|
76
65
|
its(:changeset) { should_not include :created_at }
|
77
66
|
its(:changeset) { should_not include :updated_at }
|
78
67
|
its(:previous_draft) { should be_nil }
|
79
|
-
end
|
80
|
-
end
|
81
68
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
69
|
+
context 'updating the update' do
|
70
|
+
before do
|
71
|
+
trashable.title = nil
|
72
|
+
trashable.draft_update
|
73
|
+
end
|
74
|
+
|
75
|
+
it { should_not be_create }
|
76
|
+
it { should be_update }
|
77
|
+
it { should_not be_destroy }
|
78
|
+
its(:event) { should eql 'update' }
|
79
|
+
its(:object) { should be_present }
|
80
|
+
its(:changeset) { should_not include :id }
|
81
|
+
its(:changeset) { should include :name }
|
82
|
+
its(:changeset) { should_not include :title }
|
83
|
+
its(:changeset) { should_not include :created_at }
|
84
|
+
its(:changeset) { should_not include :updated_at }
|
85
|
+
its(:previous_draft) { should be_nil }
|
87
86
|
end
|
88
|
-
|
89
|
-
it { should_not be_create }
|
90
|
-
it { should_not be_update }
|
91
|
-
it { should be_destroy }
|
92
|
-
it { should_not be_destroyed }
|
93
|
-
its(:event) { should eql 'destroy' }
|
94
|
-
its(:object) { should be_present }
|
95
|
-
its(:changeset) { should eql Hash.new }
|
96
|
-
its(:previous_draft) { should be_nil }
|
97
87
|
end
|
98
88
|
|
99
|
-
context 'with
|
100
|
-
|
101
|
-
|
102
|
-
|
89
|
+
context 'with `destroy` draft' do
|
90
|
+
context 'without previous draft' do
|
91
|
+
before do
|
92
|
+
trashable.save!
|
93
|
+
trashable.draft_destroy
|
94
|
+
end
|
95
|
+
|
96
|
+
it { should_not be_create }
|
97
|
+
it { should_not be_update }
|
98
|
+
it { should be_destroy }
|
99
|
+
it { should_not be_destroyed }
|
100
|
+
its(:event) { should eql 'destroy' }
|
101
|
+
its(:object) { should be_present }
|
102
|
+
its(:changeset) { should eql Hash.new }
|
103
|
+
its(:previous_draft) { should be_nil }
|
103
104
|
end
|
104
105
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
subject { trashable.draft.publish!; return trashable.reload }
|
125
|
-
it { expect { subject }.to_not raise_exception }
|
126
|
-
it { should be_published }
|
127
|
-
it { should_not be_trashed }
|
128
|
-
it { should_not be_draft }
|
129
|
-
its(:published_at) { should be_present }
|
130
|
-
its(:draft_id) { should be_nil }
|
131
|
-
its(:draft) { should be_nil }
|
132
|
-
its(:trashed_at) { should be_nil }
|
133
|
-
|
134
|
-
it 'deletes the draft' do
|
135
|
-
expect { subject }.to change(Draftsman::Draft, :count).by(-1)
|
106
|
+
context 'with previous `create` draft' do
|
107
|
+
before do
|
108
|
+
trashable.draft_creation
|
109
|
+
trashable.draft_destroy
|
110
|
+
end
|
111
|
+
|
112
|
+
it { should_not be_create }
|
113
|
+
it { should_not be_update }
|
114
|
+
it { should be_destroy }
|
115
|
+
it { should_not be_destroyed }
|
116
|
+
its(:event) { should eql 'destroy' }
|
117
|
+
its(:object) { should be_present }
|
118
|
+
its(:changeset) { should include :id }
|
119
|
+
its(:changeset) { should include :name }
|
120
|
+
its(:changeset) { should_not include :title }
|
121
|
+
its(:changeset) { should include :created_at }
|
122
|
+
its(:changeset) { should include :updated_at }
|
123
|
+
its(:previous_draft) { should be_present }
|
124
|
+
end
|
136
125
|
end
|
137
126
|
end
|
138
127
|
|
139
|
-
|
140
|
-
|
141
|
-
trashable.
|
142
|
-
trashable.
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
its(:name) { should eql 'Sam' }
|
152
|
-
its(:published_at) { should be_present }
|
153
|
-
its(:draft_id) { should be_nil }
|
154
|
-
its(:draft) { should be_nil }
|
155
|
-
its(:trashed_at) { should be_nil }
|
156
|
-
|
157
|
-
it 'deletes the draft' do
|
158
|
-
expect { subject }.to change(Draftsman::Draft, :count).by(-1)
|
159
|
-
end
|
128
|
+
describe :publish! do
|
129
|
+
context 'with `create` draft' do
|
130
|
+
before { trashable.draft_creation }
|
131
|
+
subject { trashable.draft.publish!; return trashable.reload }
|
132
|
+
it { expect { subject }.to_not raise_exception }
|
133
|
+
it { should be_published }
|
134
|
+
it { should_not be_trashed }
|
135
|
+
it { should_not be_draft }
|
136
|
+
its(:published_at) { should be_present }
|
137
|
+
its(:draft_id) { should be_nil }
|
138
|
+
its(:draft) { should be_nil }
|
139
|
+
its(:trashed_at) { should be_nil }
|
160
140
|
|
161
|
-
|
162
|
-
|
141
|
+
it 'deletes the draft' do
|
142
|
+
expect { subject }.to change(Draftsman::Draft, :count).by(-1)
|
143
|
+
end
|
163
144
|
end
|
164
|
-
end
|
165
145
|
|
166
|
-
|
167
|
-
context 'without previous draft' do
|
146
|
+
context 'with `update` draft' do
|
168
147
|
before do
|
169
148
|
trashable.save!
|
170
|
-
trashable.
|
149
|
+
trashable.name = 'Sam'
|
150
|
+
trashable.draft_update
|
171
151
|
end
|
172
152
|
|
173
|
-
subject { trashable.draft.publish
|
153
|
+
subject { trashable.draft.publish!; return trashable.reload }
|
154
|
+
it { expect { subject }.to_not raise_exception }
|
155
|
+
it { should be_published }
|
156
|
+
it { should_not be_draft }
|
157
|
+
it { should_not be_trashed }
|
158
|
+
its(:name) { should eql 'Sam' }
|
159
|
+
its(:published_at) { should be_present }
|
160
|
+
its(:draft_id) { should be_nil }
|
161
|
+
its(:draft) { should be_nil }
|
162
|
+
its(:trashed_at) { should be_nil }
|
174
163
|
|
175
|
-
it '
|
164
|
+
it 'deletes the draft' do
|
176
165
|
expect { subject }.to change(Draftsman::Draft, :count).by(-1)
|
177
166
|
end
|
178
167
|
|
179
|
-
it '
|
180
|
-
expect { subject }.
|
168
|
+
it 'does not delete the associated item' do
|
169
|
+
expect { subject }.to_not change(Trashable, :count)
|
181
170
|
end
|
182
171
|
end
|
183
172
|
|
184
|
-
context 'with
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
173
|
+
context 'with `destroy` draft' do
|
174
|
+
context 'without previous draft' do
|
175
|
+
before do
|
176
|
+
trashable.save!
|
177
|
+
trashable.draft_destroy
|
178
|
+
end
|
189
179
|
|
190
|
-
|
180
|
+
subject { trashable.draft.publish! }
|
191
181
|
|
192
|
-
|
193
|
-
|
194
|
-
|
182
|
+
it 'destroys the draft' do
|
183
|
+
expect { subject }.to change(Draftsman::Draft, :count).by(-1)
|
184
|
+
end
|
195
185
|
|
196
|
-
|
197
|
-
|
186
|
+
it 'deletes the associated item' do
|
187
|
+
expect { subject }.to change(Trashable, :count).by(-1)
|
188
|
+
end
|
198
189
|
end
|
199
|
-
end
|
200
|
-
end
|
201
|
-
end
|
202
190
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
191
|
+
context 'with previous `create` draft' do
|
192
|
+
before do
|
193
|
+
trashable.draft_creation
|
194
|
+
trashable.draft_destroy
|
195
|
+
end
|
208
196
|
|
209
|
-
|
210
|
-
expect { subject }.to change(Draftsman::Draft, :count).by(-1)
|
211
|
-
end
|
197
|
+
subject { trashable.draft.publish! }
|
212
198
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
end
|
199
|
+
it 'destroys the draft' do
|
200
|
+
expect { subject }.to change(Draftsman::Draft, :count).by(-1)
|
201
|
+
end
|
217
202
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
trashable.draft_update
|
203
|
+
it 'deletes the associated item' do
|
204
|
+
expect { subject }.to change(Trashable, :count).by(-1)
|
205
|
+
end
|
206
|
+
end
|
223
207
|
end
|
208
|
+
end
|
224
209
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
its(:draft) { should be_nil }
|
210
|
+
describe :revert! do
|
211
|
+
context 'with `create` draft' do
|
212
|
+
before { trashable.draft_creation }
|
213
|
+
subject { trashable.draft.revert! }
|
214
|
+
it { expect { subject }.to_not raise_exception }
|
231
215
|
|
232
|
-
|
233
|
-
|
234
|
-
|
216
|
+
it 'deletes the draft' do
|
217
|
+
expect { subject }.to change(Draftsman::Draft, :count).by(-1)
|
218
|
+
end
|
235
219
|
|
236
|
-
|
237
|
-
|
220
|
+
it 'deletes associated item' do
|
221
|
+
expect { subject }.to change(Trashable, :count).by(-1)
|
222
|
+
end
|
238
223
|
end
|
239
|
-
end
|
240
224
|
|
241
|
-
|
242
|
-
context 'without previous draft' do
|
225
|
+
context 'with `update` draft' do
|
243
226
|
before do
|
244
227
|
trashable.save!
|
245
|
-
trashable.
|
228
|
+
trashable.name = 'Sam'
|
229
|
+
trashable.draft_update
|
246
230
|
end
|
247
231
|
|
248
232
|
subject { trashable.draft.revert!; return trashable.reload }
|
249
233
|
it { expect { subject }.to_not raise_exception }
|
250
|
-
it { should_not be_trashed }
|
251
234
|
it { should_not be_draft }
|
235
|
+
its(:name) { should eql 'Bob' }
|
252
236
|
its(:draft_id) { should be_nil }
|
253
237
|
its(:draft) { should be_nil }
|
254
|
-
its(:trashed_at) { should be_nil }
|
255
238
|
|
256
239
|
it 'deletes the draft' do
|
257
240
|
expect { subject }.to change(Draftsman::Draft, :count).by(-1)
|
@@ -262,112 +245,137 @@ describe Draftsman::Draft do
|
|
262
245
|
end
|
263
246
|
end
|
264
247
|
|
265
|
-
context 'with
|
266
|
-
|
267
|
-
|
268
|
-
|
248
|
+
context 'with `destroy` draft' do
|
249
|
+
context 'without previous draft' do
|
250
|
+
before do
|
251
|
+
trashable.save!
|
252
|
+
trashable.draft_destroy
|
253
|
+
end
|
254
|
+
|
255
|
+
subject { trashable.draft.revert!; return trashable.reload }
|
256
|
+
it { expect { subject }.to_not raise_exception }
|
257
|
+
it { should_not be_trashed }
|
258
|
+
it { should_not be_draft }
|
259
|
+
its(:draft_id) { should be_nil }
|
260
|
+
its(:draft) { should be_nil }
|
261
|
+
its(:trashed_at) { should be_nil }
|
262
|
+
|
263
|
+
it 'deletes the draft' do
|
264
|
+
expect { subject }.to change(Draftsman::Draft, :count).by(-1)
|
265
|
+
end
|
266
|
+
|
267
|
+
it 'does not delete the associated item' do
|
268
|
+
expect { subject }.to_not change(Trashable, :count)
|
269
|
+
end
|
269
270
|
end
|
270
271
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
272
|
+
context 'with previous `create` draft' do
|
273
|
+
before do
|
274
|
+
trashable.draft_creation
|
275
|
+
trashable.draft_destroy
|
276
|
+
end
|
277
|
+
|
278
|
+
subject { trashable.draft.revert!; return trashable.reload }
|
279
|
+
it { expect { subject }.to_not raise_exception }
|
280
|
+
it { should_not be_trashed }
|
281
|
+
it { should be_draft }
|
282
|
+
its(:draft_id) { should be_present }
|
283
|
+
its(:draft) { should be_present }
|
284
|
+
its(:trashed_at) { should be_nil }
|
285
|
+
|
286
|
+
it 'deletes the `destroy` draft' do
|
287
|
+
expect { subject }.to change(Draftsman::Draft.where(:event => 'destroy'), :count).by(-1)
|
288
|
+
end
|
289
|
+
|
290
|
+
it 'reifies the previous `create` draft' do
|
291
|
+
expect { subject }.to change(Draftsman::Draft.where(:event => 'create'), :count).by(1)
|
292
|
+
end
|
293
|
+
|
294
|
+
it 'does not delete the associated item' do
|
295
|
+
expect { subject }.to_not change(Trashable, :count)
|
296
|
+
end
|
297
|
+
|
298
|
+
its "draft's previous draft should be nil" do
|
299
|
+
subject.draft.previous_draft.should be_nil
|
300
|
+
end
|
281
301
|
end
|
302
|
+
end
|
303
|
+
end
|
282
304
|
|
283
|
-
|
284
|
-
|
285
|
-
end
|
305
|
+
describe :reify do
|
306
|
+
subject { trashable.draft.reify }
|
286
307
|
|
287
|
-
|
288
|
-
|
289
|
-
|
308
|
+
context 'with `create` draft' do
|
309
|
+
before { trashable.draft_creation }
|
310
|
+
its(:title) { should eql trashable.title }
|
311
|
+
|
312
|
+
context 'updated create' do
|
313
|
+
before do
|
314
|
+
trashable.name = 'Sam'
|
315
|
+
trashable.draft_update
|
316
|
+
end
|
290
317
|
|
291
|
-
|
292
|
-
|
318
|
+
its(:name) { should eql 'Sam' }
|
319
|
+
its(:title) { should be_nil }
|
293
320
|
end
|
294
321
|
end
|
295
|
-
end
|
296
|
-
end
|
297
|
-
|
298
|
-
describe :reify do
|
299
|
-
subject { trashable.draft.reify }
|
300
322
|
|
301
|
-
|
302
|
-
before { trashable.draft_creation }
|
303
|
-
its(:title) { should eql trashable.title }
|
304
|
-
|
305
|
-
context 'updated create' do
|
323
|
+
context 'with `update` draft' do
|
306
324
|
before do
|
325
|
+
trashable.save!
|
307
326
|
trashable.name = 'Sam'
|
327
|
+
trashable.title = 'My Title'
|
308
328
|
trashable.draft_update
|
309
329
|
end
|
310
330
|
|
311
331
|
its(:name) { should eql 'Sam' }
|
312
|
-
its(:title) { should
|
313
|
-
end
|
314
|
-
end
|
315
|
-
|
316
|
-
context 'with `update` draft' do
|
317
|
-
before do
|
318
|
-
trashable.save!
|
319
|
-
trashable.name = 'Sam'
|
320
|
-
trashable.title = 'My Title'
|
321
|
-
trashable.draft_update
|
322
|
-
end
|
332
|
+
its(:title) { should eql 'My Title' }
|
323
333
|
|
324
|
-
|
325
|
-
|
334
|
+
context 'updating the update' do
|
335
|
+
before do
|
336
|
+
trashable.title = nil
|
337
|
+
trashable.draft_update
|
338
|
+
end
|
326
339
|
|
327
|
-
|
328
|
-
|
329
|
-
trashable.title = nil
|
330
|
-
trashable.draft_update
|
340
|
+
its(:name) { should eql 'Sam' }
|
341
|
+
its(:title) { should be_nil }
|
331
342
|
end
|
332
|
-
|
333
|
-
its(:name) { should eql 'Sam' }
|
334
|
-
its(:title) { should be_nil }
|
335
343
|
end
|
336
|
-
end
|
337
344
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
its(:name) { should eql 'Bob' }
|
346
|
-
its(:title) { should be_nil }
|
347
|
-
end
|
345
|
+
context 'with `destroy` draft' do
|
346
|
+
context 'without previous draft' do
|
347
|
+
before do
|
348
|
+
trashable.save!
|
349
|
+
trashable.draft_destroy
|
350
|
+
end
|
348
351
|
|
349
|
-
|
350
|
-
|
351
|
-
trashable.draft_creation
|
352
|
-
trashable.draft_destroy
|
352
|
+
its(:name) { should eql 'Bob' }
|
353
|
+
its(:title) { should be_nil }
|
353
354
|
end
|
354
355
|
|
355
|
-
|
356
|
-
|
357
|
-
|
356
|
+
context 'with previous `create` draft' do
|
357
|
+
before do
|
358
|
+
trashable.draft_creation
|
359
|
+
trashable.draft_destroy
|
360
|
+
end
|
358
361
|
|
359
|
-
|
360
|
-
|
361
|
-
trashable.save!
|
362
|
-
trashable.name = 'Sam'
|
363
|
-
trashable.title = 'My Title'
|
364
|
-
trashable.draft_update
|
365
|
-
# Typically, 2 draft operations won't happen in the same request, so reload before draft-destroying.
|
366
|
-
trashable.reload.draft_destroy
|
362
|
+
its(:name) { should eql 'Bob' }
|
363
|
+
its(:title) { should be_nil }
|
367
364
|
end
|
368
365
|
|
369
|
-
|
370
|
-
|
366
|
+
context 'with previous `update` draft' do
|
367
|
+
before do
|
368
|
+
trashable.save!
|
369
|
+
trashable.name = 'Sam'
|
370
|
+
trashable.title = 'My Title'
|
371
|
+
trashable.draft_update
|
372
|
+
# Typically, 2 draft operations won't happen in the same request, so reload before draft-destroying.
|
373
|
+
trashable.reload.draft_destroy
|
374
|
+
end
|
375
|
+
|
376
|
+
its(:name) { should eql 'Sam' }
|
377
|
+
its(:title) { should eql 'My Title' }
|
378
|
+
end
|
371
379
|
end
|
372
380
|
end
|
373
381
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: draftsman
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Peters
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -165,8 +165,10 @@ files:
|
|
165
165
|
- lib/draftsman/version.rb
|
166
166
|
- lib/generators/draftsman/install_generator.rb
|
167
167
|
- lib/generators/draftsman/templates/add_object_changes_column_to_drafts.rb
|
168
|
+
- lib/generators/draftsman/templates/add_object_changes_column_to_drafts_json.rb
|
168
169
|
- lib/generators/draftsman/templates/config/initializers/draftsman.rb
|
169
170
|
- lib/generators/draftsman/templates/create_drafts.rb
|
171
|
+
- lib/generators/draftsman/templates/create_drafts_json.rb
|
170
172
|
- spec/controllers/informants_controller_spec.rb
|
171
173
|
- spec/controllers/users_controller_spec.rb
|
172
174
|
- spec/controllers/whodunnits_controller_spec.rb
|