draftsman 0.2.1 → 0.3.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.
- 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
|