conscript 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +23 -0
- data/README.md +7 -17
- data/lib/conscript/orm/activerecord.rb +22 -7
- data/lib/conscript/version.rb +1 -1
- data/spec/conscript/orm/activerecord_spec.rb +29 -10
- data/spec/support/widget_migration.rb +1 -0
- metadata +15 -14
data/CHANGELOG.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
## v0.2.1
|
2
|
+
|
3
|
+
* Fixed bug where associated `has_many` records with CarrierWave uploads were not duplicated correctly - uploaded files are now copied
|
4
|
+
* Fixed bug where destroying a draft would remove an uploaded file from its draft parent if they shared the same file.
|
5
|
+
* Fixed bug where drafts were destroyed before publish_Draft was successful
|
6
|
+
* Added an ActiveRecord error where `save` fails if drafts exist for an instance
|
7
|
+
|
8
|
+
## v0.2.0
|
9
|
+
|
10
|
+
* Now defines a `published` scope rather than a `default_scope`. This avoids a number of complexities especially around validations and polymorphic relationships.
|
11
|
+
|
12
|
+
## v0.1.1
|
13
|
+
|
14
|
+
* CarrierWave uplaods on associated records are now cloned correctly.
|
15
|
+
|
16
|
+
## v0.1.0
|
17
|
+
|
18
|
+
* Additional callbacks added to allow bespoke behaviour around the save as draft and publish draft lifecycles.
|
19
|
+
* Additional options added to remove previously documented limitations.
|
20
|
+
|
21
|
+
## v0.0.1
|
22
|
+
|
23
|
+
* First release.
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Conscript
|
2
2
|
|
3
|
-
Provides ActiveRecord models with
|
3
|
+
Provides ActiveRecord models with `drafts` and `published` scopes, and the functionality to create draft instances and publish them.
|
4
4
|
|
5
5
|
Existing instances may have one or more draft versions which are initially created by duplication, including any required associations. A draft may then be published, overwriting the original instance.
|
6
6
|
|
@@ -46,13 +46,16 @@ To use the drafts functionality, call `register_for_draft` in your ActiveRecord
|
|
46
46
|
register_for_draft
|
47
47
|
end
|
48
48
|
|
49
|
-
|
49
|
+
This registers `published` and `drafts` scopes on the model. You may use these scopes as with any other scope:
|
50
|
+
|
51
|
+
Article.published
|
52
|
+
|
53
|
+
Or:
|
50
54
|
|
51
55
|
Article.drafts
|
52
56
|
|
53
|
-
|
57
|
+
You'll therefore need to modify any existing code to use the `published` scope where you don't want to show drafts.
|
54
58
|
|
55
|
-
Article.unscoped.all
|
56
59
|
|
57
60
|
### Instance methods
|
58
61
|
|
@@ -127,19 +130,6 @@ This will result in uploads for drafts being stored in the same location as the
|
|
127
130
|
|
128
131
|
Conscript also overrides CarrierWave's `#destroy` callbacks to ensure that no other instance is using the same file before deleting it from the filesystem. Otherwise this can happen when you delete a draft with the same file as the original instance.
|
129
132
|
|
130
|
-
|
131
|
-
### Gotchas
|
132
|
-
|
133
|
-
If saving drafts of models with `has_many` associations, the associated model should define a reciprocal `belongs_to` relationship (as normal). However, if it also has a presence validation e.g. `validates parent_model_name, presence: true` then you will encounter a limitation of ActiveRecord's default scopes. To avoid this when saving drafts with associations, wrap your save code with a `Model.unscoped` block, e.g:
|
134
|
-
|
135
|
-
Article.unscoped { draft = article.save_as_draft! }
|
136
|
-
|
137
|
-
Or if updating an existing draft:
|
138
|
-
|
139
|
-
Article.unscoped { draft.save! }
|
140
|
-
|
141
|
-
Otherwise you'll encounter a validation error.
|
142
|
-
|
143
133
|
## Contributing
|
144
134
|
|
145
135
|
1. Fork it
|
@@ -21,15 +21,13 @@ module Conscript
|
|
21
21
|
self.conscript_options[:ignore_attributes].map!(&:to_s)
|
22
22
|
self.conscript_options.update options.slice(:allow_update_with_drafts, :destroy_drafts_on_publish)
|
23
23
|
|
24
|
-
default_scope { where(is_draft: false) }
|
25
|
-
|
26
24
|
belongs_to :draft_parent, class_name: self
|
27
25
|
has_many :drafts, conditions: {is_draft: true}, class_name: self, foreign_key: :draft_parent_id, dependent: :destroy, inverse_of: :draft_parent
|
28
26
|
|
29
27
|
define_callbacks :publish_draft, :save_as_draft
|
30
28
|
|
31
29
|
before_save :check_no_drafts_exist if (self.conscript_options[:allow_update_with_drafts] == false)
|
32
|
-
set_callback :publish_draft, :
|
30
|
+
set_callback :publish_draft, :after, :destroy_all_drafts if (self.conscript_options[:destroy_drafts_on_publish] == true)
|
33
31
|
|
34
32
|
# Prevent deleting CarrierWave uploads which may be used by other instances. Uploaders must be mounted beforehand.
|
35
33
|
if self.respond_to? :uploaders
|
@@ -38,6 +36,10 @@ module Conscript
|
|
38
36
|
end
|
39
37
|
|
40
38
|
class_eval <<-RUBY
|
39
|
+
def self.published
|
40
|
+
where(is_draft: false)
|
41
|
+
end
|
42
|
+
|
41
43
|
def self.drafts
|
42
44
|
where(is_draft: true)
|
43
45
|
end
|
@@ -45,10 +47,15 @@ module Conscript
|
|
45
47
|
def save_as_draft!
|
46
48
|
run_callbacks :save_as_draft do
|
47
49
|
raise Conscript::Exception::AlreadyDraft if is_draft?
|
48
|
-
draft = new_record? ? self : dup(include: self.class.conscript_options[:associations])
|
50
|
+
draft = new_record? ? self : dup(include: self.class.conscript_options[:associations]) do |original, dup|
|
51
|
+
# Workaround for CarrierWave uploaders on associated records. Copy the uploaded files.
|
52
|
+
if dup.class.respond_to? :uploaders
|
53
|
+
dup.class.uploaders.keys.each {|uploader| dup.send(uploader.to_s + "=", original.send(uploader)) }
|
54
|
+
end
|
55
|
+
end
|
49
56
|
draft.is_draft = true
|
50
57
|
draft.draft_parent = self unless new_record?
|
51
|
-
|
58
|
+
draft.save!
|
52
59
|
draft
|
53
60
|
end
|
54
61
|
end
|
@@ -63,10 +70,16 @@ module Conscript
|
|
63
70
|
self.class.conscript_options[:associations].each do |association|
|
64
71
|
case reflections[association].macro
|
65
72
|
when :has_many
|
66
|
-
draft_parent.send(association.to_s + "=", self.send(association).collect {|child| child.dup
|
73
|
+
draft_parent.send(association.to_s + "=", self.send(association).collect {|child| child.dup do |original, dup|
|
74
|
+
# Workaround for CarrierWave uploaders on associated records. Copy the uploaded files.
|
75
|
+
if dup.class.respond_to? :uploaders
|
76
|
+
dup.class.uploaders.keys.each {|uploader| dup.send(uploader.to_s + "=", original.send(uploader)) }
|
77
|
+
end
|
78
|
+
end })
|
67
79
|
end
|
68
80
|
end
|
69
81
|
|
82
|
+
self.destroy
|
70
83
|
draft_parent.save!
|
71
84
|
end
|
72
85
|
draft_parent
|
@@ -79,6 +92,7 @@ module Conscript
|
|
79
92
|
|
80
93
|
private
|
81
94
|
def check_no_drafts_exist
|
95
|
+
errors[:base] << "Cannot save record while drafts exist"
|
82
96
|
drafts.count == 0
|
83
97
|
end
|
84
98
|
|
@@ -91,7 +105,8 @@ module Conscript
|
|
91
105
|
def clean_uploaded_files_for_draft!
|
92
106
|
self.class.uploaders.keys.each do |attribute|
|
93
107
|
filename = attributes[attribute.to_s]
|
94
|
-
|
108
|
+
cols = self.class.arel_table
|
109
|
+
self.send("remove_" + attribute.to_s + "!") if !draft_parent_id or self.class.where(cols[:id].eq(draft_parent_id).or(cols[:draft_parent_id].eq(draft_parent_id))).where(attribute => filename).count == 0
|
95
110
|
end
|
96
111
|
end
|
97
112
|
|
data/lib/conscript/version.rb
CHANGED
@@ -11,11 +11,6 @@ describe Conscript::ActiveRecord do
|
|
11
11
|
Widget.respond_to?(:register_for_draft).should == true
|
12
12
|
end
|
13
13
|
|
14
|
-
it "creates the default scope" do
|
15
|
-
Widget.should_receive(:default_scope).once
|
16
|
-
Widget.register_for_draft
|
17
|
-
end
|
18
|
-
|
19
14
|
it "creates a belongs_to association" do
|
20
15
|
Widget.should_receive(:belongs_to).once.with(:draft_parent, kind_of(Hash))
|
21
16
|
Widget.register_for_draft
|
@@ -60,7 +55,7 @@ describe Conscript::ActiveRecord do
|
|
60
55
|
Widget.register_for_draft(destroy_drafts_on_publish: true)
|
61
56
|
callback = Widget._publish_draft_callbacks.first
|
62
57
|
callback.filter.should == :destroy_all_drafts
|
63
|
-
callback.kind.should == :
|
58
|
+
callback.kind.should == :after
|
64
59
|
end
|
65
60
|
end
|
66
61
|
|
@@ -100,6 +95,14 @@ describe Conscript::ActiveRecord do
|
|
100
95
|
end
|
101
96
|
end
|
102
97
|
|
98
|
+
describe "#published" do
|
99
|
+
it "limits results to published" do
|
100
|
+
Widget.register_for_draft
|
101
|
+
Widget.should_receive(:where).once.with(is_draft: false)
|
102
|
+
Widget.published
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
103
106
|
describe "#drafts" do
|
104
107
|
it "limits results to drafts" do
|
105
108
|
Widget.register_for_draft
|
@@ -123,6 +126,7 @@ describe Conscript::ActiveRecord do
|
|
123
126
|
@subject.send(:check_no_drafts_exist).should == true
|
124
127
|
end
|
125
128
|
end
|
129
|
+
|
126
130
|
context "when drafts exist" do
|
127
131
|
before do
|
128
132
|
@subject.stub_chain(:drafts, :count).and_return(1)
|
@@ -131,6 +135,12 @@ describe Conscript::ActiveRecord do
|
|
131
135
|
it "returns false" do
|
132
136
|
@subject.send(:check_no_drafts_exist).should == false
|
133
137
|
end
|
138
|
+
|
139
|
+
it "adds an error" do
|
140
|
+
@subject.errors[:base].should be_empty
|
141
|
+
@subject.send(:check_no_drafts_exist)
|
142
|
+
@subject.errors[:base].should_not be_empty
|
143
|
+
end
|
134
144
|
end
|
135
145
|
end
|
136
146
|
|
@@ -235,7 +245,7 @@ describe Conscript::ActiveRecord do
|
|
235
245
|
context "and has associations" do
|
236
246
|
before do
|
237
247
|
@subject.save!
|
238
|
-
@subject.thingies.create(name: 'Thingy')
|
248
|
+
@subject.thingies.create(name: 'Thingy', file: 'test.jpg')
|
239
249
|
end
|
240
250
|
|
241
251
|
context "and the association is not specified in register_for_draft" do
|
@@ -250,17 +260,26 @@ describe Conscript::ActiveRecord do
|
|
250
260
|
end
|
251
261
|
|
252
262
|
context "and the association is specified in register_for_draft" do
|
253
|
-
|
263
|
+
it "duplicates the associated records" do
|
254
264
|
Widget.register_for_draft associations: :thingies
|
255
265
|
@duplicate = @subject.save_as_draft!
|
256
|
-
end
|
257
266
|
|
258
|
-
it "duplicates the associated records" do
|
259
267
|
@subject.thingies.count.should == 1
|
260
268
|
@duplicate.thingies.count.should == 1
|
261
269
|
@duplicate.thingies.first.name.should == @subject.thingies.first.name
|
262
270
|
@duplicate.thingies.first.id.should_not == @subject.thingies.first.id
|
263
271
|
end
|
272
|
+
|
273
|
+
it "copies the uploader attributes using the setter" do
|
274
|
+
Thingy.cattr_accessor :uploaders do
|
275
|
+
{file: nil}
|
276
|
+
end
|
277
|
+
|
278
|
+
Widget.register_for_draft associations: :thingies
|
279
|
+
|
280
|
+
Thingy.any_instance.should_receive(:file=)
|
281
|
+
@duplicate = @subject.save_as_draft!
|
282
|
+
end
|
264
283
|
end
|
265
284
|
end
|
266
285
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: conscript
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-07-
|
12
|
+
date: 2013-07-12 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
16
|
-
requirement: &
|
16
|
+
requirement: &70350313257540 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 1.3.5
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70350313257540
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rake
|
27
|
-
requirement: &
|
27
|
+
requirement: &70350313257120 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70350313257120
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rspec
|
38
|
-
requirement: &
|
38
|
+
requirement: &70350313256600 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70350313256600
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: sqlite3
|
49
|
-
requirement: &
|
49
|
+
requirement: &70350313256180 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70350313256180
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: activerecord
|
60
|
-
requirement: &
|
60
|
+
requirement: &70350313255560 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ~>
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: 3.2.13
|
66
66
|
type: :runtime
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70350313255560
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: deep_cloneable
|
71
|
-
requirement: &
|
71
|
+
requirement: &70350313254920 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ~>
|
@@ -76,7 +76,7 @@ dependencies:
|
|
76
76
|
version: 1.5.2
|
77
77
|
type: :runtime
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *70350313254920
|
80
80
|
description: Provides ActiveRecord models with draft instances, including associations
|
81
81
|
email:
|
82
82
|
- steve@stevelorek.com
|
@@ -86,6 +86,7 @@ extra_rdoc_files: []
|
|
86
86
|
files:
|
87
87
|
- .gitignore
|
88
88
|
- .rspec
|
89
|
+
- CHANGELOG.md
|
89
90
|
- Gemfile
|
90
91
|
- LICENSE.txt
|
91
92
|
- README.md
|