conscript 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in conscript.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Steve Lorek
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,133 @@
1
+ # Conscript
2
+
3
+ Provides ActiveRecord models with a `drafts` scope, and the functionality to create draft instances and publish them.
4
+
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
+
7
+ Alternatively, draft instances may be created from scratch and published later.
8
+
9
+ The approach of the gem differs from others in that it does not create extra tables or serialise objects; instead it uses ActiveRecord's built-in scoping so that drafts have all of the same functionality as any other instance.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'conscript'
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install conscript
24
+
25
+ ## Usage
26
+
27
+ All models which wish to register for the draft must add two columns to their database schema, as illustrated in this example migration:
28
+
29
+ class AddDraftColumns < ActiveRecord::Migration
30
+ def self.up
31
+ add_column :table_name, :draft_parent_id, :integer
32
+ add_column :table_name, :is_draft, :boolean, default: false
33
+ add_index :table_name, :draft_parent_id
34
+ add_index :table_name, :is_draft
35
+ end
36
+
37
+ def self.down
38
+ remove_column :table_name, :draft_parent_id
39
+ remove_column :table_name, :is_draft
40
+ end
41
+ end
42
+
43
+ To use the drafts functionality, call `register_for_draft` in your ActiveRecord model:
44
+
45
+ class Article < ActiveRecord::Base
46
+ register_for_draft
47
+ end
48
+
49
+ A `default_scope` is then set to exclude all draft instances from finders. You may use the `drafts` scope to access them:
50
+
51
+ Article.drafts
52
+
53
+ If you need to access drafts and original instances together, use `unscoped` as you would any other `default_scope`:
54
+
55
+ Article.unscoped.all
56
+
57
+ ### Instance methods
58
+
59
+ Draft instances may optionally be created from existing instances:
60
+
61
+ article = Article.first
62
+ draft = article.save_as_draft!
63
+
64
+ Or you can create drafts from scratch:
65
+
66
+ Article.new.save_as_draft!
67
+
68
+ You can access the original instance from the draft:
69
+
70
+ draft.draft_parent == article # => true
71
+
72
+ And you can also access all of an instance's drafts:
73
+
74
+ article.drafts # => [draft]
75
+
76
+ To determine whether an instance is a draft:
77
+
78
+ article.is_draft? # => false
79
+ draft.is_draft? # => true
80
+
81
+ Publish a draft to overwrite the original instance:
82
+
83
+ draft.publish_draft # returns the original, updated instance
84
+
85
+
86
+ ### Options
87
+
88
+ By default, drafts are created with `ActiveRecord::Base#dup`, i.e. a shallow copy of all attributes, without any associations.
89
+
90
+ Options may be passed to the `register_for_draft` method, e.g:
91
+
92
+ register_for_draft associations: :tags, ignore_attributes: :cached_slug
93
+
94
+ A list of options are below:
95
+
96
+ - `:associations` an array of `has_many` association names to duplicate. These will be copied to the draft and overwrite the original instance's when published. Deep cloning is possible thanks to the [`deep_cloneable`](https://github.com/moiristo/deep_cloneable) gem. Refer to the `deep_cloneable` documentation to get an idea of how far you can go with this. Please note: `belongs_to` associations aren't supported as these should be drafted separately.
97
+ - `:ignore_attributes` an array of attribute names which should _not_ be duplicated. Timestamps and STI `type` columns are excluded by default. Don't include association names here.
98
+
99
+
100
+ ### Using with CarrierWave
101
+
102
+ Conscript supports [CarrierWave](https://github.com/carrierwaveuploader/carrierwave) uploads, but there's a couple of things you should be aware of.
103
+
104
+ First, you must ensure `register_for_draft` is called _after_ any calls to `mount_uploader`.
105
+
106
+ Then, in your uploaders where `store_dir` is defined, if you are organising file storage by model instance (e.g. `#to_param`) then you should use the new model method `uploader_store_param` to define the unique location, e.g:
107
+
108
+ class ArticleImageUploader < ImageUploader
109
+ def store_dir
110
+ "public/images/articles/#{model.uploader_store_param}"
111
+ end
112
+ end
113
+
114
+ This will result in uploads for drafts being stored in the same location as the original instance. This is because Conscript does not want to have to worry about moving files when publishing an instance.
115
+
116
+ 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.
117
+
118
+
119
+ ### Limitations
120
+
121
+ For reasons of sanity:
122
+
123
+ - You cannot make changes to an instance if it has drafts, this is because it would be difficult to propogate those changes down or provide visibility of the changes.
124
+ - When you publish a draft, any other drafts for the same `draft_parent` are also destroyed.
125
+
126
+
127
+ ## Contributing
128
+
129
+ 1. Fork it
130
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
131
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
132
+ 4. Push to the branch (`git push origin my-new-feature`)
133
+ 5. Create new Pull Request
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task default: :spec
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'conscript/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "conscript"
8
+ spec.version = Conscript::VERSION
9
+ spec.authors = ["Steve Lorek"]
10
+ spec.email = ["steve@stevelorek.com"]
11
+ spec.description = %q{Provides ActiveRecord models with draft instances, including associations}
12
+ spec.summary = %q{Provides ActiveRecord models with draft instances, including associations}
13
+ spec.homepage = "http://github.com/slorek/conscript"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3.5"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "sqlite3"
25
+ spec.add_runtime_dependency "activerecord", "~> 3.2.13"
26
+ spec.add_runtime_dependency "deep_cloneable", "~> 1.5.2"
27
+ end
@@ -0,0 +1,6 @@
1
+ require "conscript/version"
2
+ require 'conscript/orm/activerecord'
3
+
4
+ module Conscript
5
+ # Your code goes here...
6
+ end
@@ -0,0 +1,6 @@
1
+ module Conscript
2
+ module Exception
3
+ class AlreadyDraft < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Conscript
2
+ module Exception
3
+ class NotADraft < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,93 @@
1
+ require 'active_record'
2
+ require 'deep_cloneable'
3
+ require 'conscript/exception/already_draft'
4
+ require 'conscript/exception/not_a_draft'
5
+
6
+ module Conscript
7
+ module ActiveRecord
8
+ def register_for_draft(options = {})
9
+
10
+ cattr_accessor :conscript_options, :instance_accessor => false do
11
+ {
12
+ associations: [],
13
+ ignore_attributes: [self.primary_key, 'type', 'created_at', 'updated_at', 'draft_parent_id', 'is_draft']
14
+ }
15
+ end
16
+
17
+ self.conscript_options.each_pair {|key, value| self.conscript_options[key] = Array(value) | Array(options[key]) }
18
+ self.conscript_options[:associations].map!(&:to_sym)
19
+ self.conscript_options[:ignore_attributes].map!(&:to_s)
20
+
21
+ default_scope { where(is_draft: false) }
22
+
23
+ belongs_to :draft_parent, class_name: self
24
+ has_many :drafts, conditions: {is_draft: true}, class_name: self, foreign_key: :draft_parent_id, dependent: :destroy, inverse_of: :draft_parent
25
+
26
+ before_save :check_no_drafts_exist
27
+
28
+ # Prevent deleting CarrierWave uploads which may be used by other instances. Uploaders must be mounted beforehand.
29
+ if self.respond_to? :uploaders
30
+ self.uploaders.keys.each {|attribute| skip_callback :commit, :after, :"remove_#{attribute}!" }
31
+ after_commit :clean_uploaded_files_for_draft!, :on => :destroy
32
+ end
33
+
34
+ class_eval <<-RUBY
35
+ def self.drafts
36
+ where(is_draft: true)
37
+ end
38
+
39
+ def save_as_draft!
40
+ raise Conscript::Exception::AlreadyDraft if is_draft?
41
+ draft = new_record? ? self : dup(include: self.class.conscript_options[:associations])
42
+ draft.is_draft = true
43
+ draft.draft_parent = self unless new_record?
44
+ draft.save!
45
+ draft
46
+ end
47
+
48
+ def publish_draft
49
+ raise Conscript::Exception::NotADraft unless is_draft?
50
+ return self.update_attribute(:is_draft, false) if !draft_parent_id
51
+ ::ActiveRecord::Base.transaction do
52
+ draft_parent.assign_attributes attributes_to_publish, without_protection: true
53
+
54
+ self.class.conscript_options[:associations].each do |association|
55
+ case reflections[association].macro
56
+ when :has_many
57
+ draft_parent.send(association.to_s + "=", self.send(association).collect {|child| child.dup })
58
+ end
59
+ end
60
+
61
+ draft_parent.drafts.destroy_all
62
+ draft_parent.save!
63
+ end
64
+ draft_parent
65
+ end
66
+
67
+ def uploader_store_param
68
+ draft_parent_id.nil? ? to_param : draft_parent.to_param
69
+ end
70
+
71
+ private
72
+ def check_no_drafts_exist
73
+ drafts.count == 0
74
+ end
75
+
76
+ def attributes_to_publish
77
+ attributes.reject {|attribute| self.class.conscript_options[:ignore_attributes].include?(attribute) }
78
+ end
79
+
80
+ # Clean up CarrierWave uploads if there are no other instances using the files.
81
+ #
82
+ def clean_uploaded_files_for_draft!
83
+ self.class.uploaders.keys.each do |attribute|
84
+ filename = attributes[attribute.to_s]
85
+ self.send("remove_" + attribute.to_s + "!") if !draft_parent_id or draft_parent.drafts.where(attribute => filename).count == 0
86
+ end
87
+ end
88
+ RUBY
89
+ end
90
+ end
91
+ end
92
+
93
+ ActiveRecord::Base.extend Conscript::ActiveRecord
@@ -0,0 +1,3 @@
1
+ module Conscript
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,354 @@
1
+ require 'spec_helper'
2
+
3
+ describe Conscript::ActiveRecord do
4
+
5
+ before(:all) { WidgetMigration.up }
6
+ after(:all) { WidgetMigration.down }
7
+ after { Widget.unscoped.delete_all }
8
+
9
+ describe "#register_for_draft" do
10
+ it "is defined on subclasses of ActiveRecord::Base" do
11
+ Widget.respond_to?(:register_for_draft).should == true
12
+ end
13
+
14
+ it "creates the default scope" do
15
+ Widget.should_receive(:default_scope).once
16
+ Widget.register_for_draft
17
+ end
18
+
19
+ it "creates a belongs_to association" do
20
+ Widget.should_receive(:belongs_to).once.with(:draft_parent, kind_of(Hash))
21
+ Widget.register_for_draft
22
+ end
23
+
24
+ it "creates a has_many association" do
25
+ Widget.should_receive(:has_many).once.with(:drafts, kind_of(Hash))
26
+ Widget.register_for_draft
27
+ end
28
+
29
+ it "creates a before_save callback" do
30
+ Widget.should_receive(:before_save).once.with(:check_no_drafts_exist)
31
+ Widget.register_for_draft
32
+ end
33
+
34
+ it "accepts options and merges them with defaults" do
35
+ Widget.register_for_draft(associations: :owners, ignore_attributes: :custom_attribute)
36
+ Widget.conscript_options[:associations].should == [:owners]
37
+ Widget.conscript_options[:ignore_attributes].should == ["id", "type", "created_at", "updated_at", "draft_parent_id", "is_draft", "custom_attribute"]
38
+ end
39
+
40
+ describe "CarrierWave compatibility" do
41
+ context "where no uploaders are defined on the class" do
42
+ it "does not try to skip callbacks" do
43
+ Widget.should_not_receive :skip_callback
44
+ Widget.register_for_draft
45
+ end
46
+ end
47
+
48
+ context "where uploaders are defined on the class" do
49
+ before do
50
+ Widget.cattr_accessor :uploaders
51
+ Widget.uploaders = {file: nil}
52
+ end
53
+
54
+ it "disables the provided remove_#attribute callback behaviour" do
55
+ Widget.should_receive(:skip_callback).with(:commit, :after, :remove_file!)
56
+ Widget.register_for_draft
57
+ end
58
+
59
+ it "registers a callback to #clean_uploaded_files_for_draft" do
60
+ Widget.should_receive(:after_commit).with(:clean_uploaded_files_for_draft!, :on => :destroy)
61
+ Widget.register_for_draft
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ describe "#drafts" do
68
+ it "limits results to drafts" do
69
+ Widget.register_for_draft
70
+ Widget.should_receive(:where).once.with(is_draft: true)
71
+ Widget.drafts
72
+ end
73
+ end
74
+
75
+ describe "#check_no_drafts_exist" do
76
+ before do
77
+ Widget.register_for_draft
78
+ @subject = Widget.new
79
+ end
80
+
81
+ context "when no drafts exist" do
82
+ before do
83
+ @subject.stub_chain(:drafts, :count).and_return(0)
84
+ end
85
+
86
+ it "returns true" do
87
+ @subject.send(:check_no_drafts_exist).should == true
88
+ end
89
+ end
90
+ context "when drafts exist" do
91
+ before do
92
+ @subject.stub_chain(:drafts, :count).and_return(1)
93
+ end
94
+
95
+ it "returns false" do
96
+ @subject.send(:check_no_drafts_exist).should == false
97
+ end
98
+ end
99
+ end
100
+
101
+ describe "#clean_uploaded_files_for_draft!" do
102
+ before do
103
+ Widget.cattr_accessor :uploaders
104
+ Widget.uploaders = {file: nil}
105
+ Widget.register_for_draft
106
+ end
107
+
108
+ context "where files are not shared with any other instances" do
109
+ before do
110
+ @original = Widget.create(file: 'test.jpg')
111
+ @duplicate = @original.save_as_draft!
112
+ @duplicate.file = 'another_file.jpg'
113
+ @duplicate.save
114
+ @original.reload
115
+ end
116
+
117
+ it "should not attempt to remove the file" do
118
+ @duplicate.should_receive(:remove_file!)
119
+ @duplicate.destroy
120
+ end
121
+ end
122
+
123
+ context "where files are shared with other instances" do
124
+ before do
125
+ @original = Widget.create(file: 'test.jpg')
126
+ @duplicate = @original.save_as_draft!
127
+ @duplicate.file.should == 'test.jpg'
128
+ @original.reload
129
+ end
130
+
131
+ it "should not attempt to remove the file" do
132
+ @duplicate.should_not_receive(:remove_file!)
133
+ @duplicate.destroy
134
+ end
135
+ end
136
+ end
137
+
138
+ describe "#save_as_draft!" do
139
+ before { Widget.register_for_draft }
140
+
141
+ context "where the instance is a draft" do
142
+ it "raises an exception" do
143
+ -> { Widget.new(is_draft: true).save_as_draft! }.should raise_error Conscript::Exception::AlreadyDraft
144
+ end
145
+ end
146
+
147
+ context "where the instance is not a draft" do
148
+ before do
149
+ @subject = Widget.new
150
+ end
151
+
152
+ context "and is a new record" do
153
+ before do
154
+ @subject.stub(:new_record?).and_return(true)
155
+ end
156
+
157
+ it "saves as a draft" do
158
+ @subject.should_receive("is_draft=").once.with(true)
159
+ @subject.should_not_receive("draft_parent=")
160
+ @subject.should_receive("save!").once
161
+ @subject.save_as_draft!
162
+ end
163
+
164
+ it "returns the instance" do
165
+ @subject.save_as_draft!.should == @subject
166
+ end
167
+ end
168
+
169
+ context "and is persisted" do
170
+ before do
171
+ @subject.stub(:new_record?).and_return(false)
172
+ @duplicate = Widget.new
173
+ @subject.should_receive(:dup).once.and_return(@duplicate)
174
+ end
175
+
176
+ it "saves a duplicate record as a draft" do
177
+ @duplicate.should_receive("is_draft=").once.with(true)
178
+ @duplicate.should_receive("draft_parent=").once.with(@subject)
179
+ @duplicate.should_receive("save!").once
180
+ @subject.save_as_draft!
181
+ end
182
+
183
+ it "returns the duplicate instance" do
184
+ @subject.save_as_draft!.should == @duplicate
185
+ end
186
+ end
187
+
188
+ context "and has associations" do
189
+ before do
190
+ @associated = Thingy.create(name: 'Thingy')
191
+ @subject.thingies << @associated
192
+ @subject.save
193
+ end
194
+
195
+ context "and the association is not specified in register_for_draft" do
196
+ before do
197
+ @duplicate = @subject.save_as_draft!
198
+ end
199
+
200
+ it "does not duplicate the associated records" do
201
+ @duplicate.thingies.count.should == 0
202
+ end
203
+ end
204
+
205
+ context "and the association is specified in register_for_draft" do
206
+ before do
207
+ Widget.register_for_draft associations: :thingies
208
+ @duplicate = @subject.save_as_draft!
209
+ end
210
+
211
+ it "duplicates the associated records" do
212
+ @subject.thingies.count.should == 1
213
+ @duplicate.thingies.count.should == 1
214
+ @duplicate.thingies.first.name.should == @subject.thingies.first.name
215
+ @duplicate.thingies.first.id.should_not == @subject.thingies.first.id
216
+ end
217
+ end
218
+ end
219
+ end
220
+ end
221
+
222
+ describe "#publish_draft" do
223
+ before { Widget.register_for_draft }
224
+
225
+ context "where the instance is not a draft" do
226
+ it "raises an exception" do
227
+ -> { Widget.new.publish_draft }.should raise_error Conscript::Exception::NotADraft
228
+ end
229
+ end
230
+
231
+ context "where the instance is a draft" do
232
+ context "and has no parent" do
233
+ before do
234
+ @subject = Widget.new.save_as_draft!
235
+ end
236
+
237
+ it "sets is_draft to false and saves" do
238
+ @subject.is_draft?.should == true
239
+ @subject.publish_draft
240
+ @subject.is_draft?.should == false
241
+ end
242
+ end
243
+
244
+ context "and has a parent instance" do
245
+ before do
246
+ @original = Widget.create(name: 'Old name')
247
+ @duplicate = @original.save_as_draft!
248
+ end
249
+
250
+ it "copies the attributes to the parent" do
251
+ @duplicate.name = 'New name'
252
+ @duplicate.publish_draft
253
+ @original.reload
254
+ @original.name.should == 'New name'
255
+ end
256
+
257
+ it "does not copy the ID, type, draft or timestamp attributes" do
258
+ @duplicate.name = 'New name'
259
+ @duplicate.publish_draft
260
+ @original.reload
261
+ @original.id.should_not == @duplicate.id
262
+ @original.created_at.should_not == @duplicate.created_at
263
+ @original.updated_at.should_not == @duplicate.updated_at
264
+ @original.is_draft.should_not == @duplicate.is_draft
265
+ @original.draft_parent_id.should_not == @duplicate.draft_parent_id
266
+ end
267
+
268
+ it "destroys the draft instance" do
269
+ @duplicate.publish_draft
270
+ -> { @duplicate.reload }.should raise_error(ActiveRecord::RecordNotFound)
271
+ end
272
+
273
+ it "destroys the parent's other drafts" do
274
+ 3.times { @original.save_as_draft! }
275
+ @original.drafts.count.should == 4
276
+ @duplicate.publish_draft
277
+ @original.drafts.count.should == 0
278
+ end
279
+
280
+ context "where attributes were excluded in register_for_draft" do
281
+ before { Widget.register_for_draft ignore_attributes: :name }
282
+
283
+ it "does not copy the excluded attributes" do
284
+ @duplicate.name = 'New name'
285
+ @duplicate.publish_draft
286
+ @original.reload
287
+ @original.name.should == 'Old name'
288
+ end
289
+ end
290
+
291
+ describe "copying associations" do
292
+ def setup
293
+ @original.thingies.count.should == 0
294
+ @associated = Thingy.create(name: 'Thingy')
295
+ @duplicate.thingies << @associated
296
+ @duplicate.save!
297
+ @duplicate.publish_draft
298
+ @original.reload
299
+ end
300
+
301
+ context "when associations were specified in register_for_draft" do
302
+ before do
303
+ Widget.register_for_draft associations: :thingies
304
+ setup
305
+ end
306
+
307
+ it "copies has_many associations to the parent" do
308
+ @original.thingies.count.should == 1
309
+ end
310
+ end
311
+
312
+ context "when no associations were specified in register_for_draft" do
313
+ before do
314
+ Widget.register_for_draft associations: nil
315
+ setup
316
+ end
317
+
318
+ it "does not copy associations to the parent" do
319
+ @original.thingies.count.should == 0
320
+ end
321
+ end
322
+ end
323
+ end
324
+ end
325
+ end
326
+
327
+ describe "#uploader_store_param" do
328
+ before do
329
+ Widget.register_for_draft
330
+ @original = Widget.create
331
+ @duplicate = @original.save_as_draft!
332
+ @draft = Widget.new.save_as_draft!
333
+ end
334
+
335
+ context "where the instance is not a draft" do
336
+ it "returns #to_param" do
337
+ @original.uploader_store_param.should == @original.to_param
338
+ end
339
+ end
340
+
341
+ context "where the instance is a draft" do
342
+ context "and it has no draft_parent" do
343
+ it "returns #to_param" do
344
+ @draft.uploader_store_param.should == @draft.to_param
345
+ end
346
+ end
347
+ context "and it has a draft_parent" do
348
+ it "returns draft_parent#to_param" do
349
+ @duplicate.uploader_store_param.should == @original.to_param
350
+ end
351
+ end
352
+ end
353
+ end
354
+ end
@@ -0,0 +1,5 @@
1
+ require 'conscript'
2
+ Dir["./spec/support/**/*.rb"].each {|f| require f}
3
+
4
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
5
+ ActiveRecord::Migration.verbose = false
@@ -0,0 +1,3 @@
1
+ class Thingy < ActiveRecord::Base
2
+ belongs_to :widget
3
+ end
@@ -0,0 +1,3 @@
1
+ class Widget < ActiveRecord::Base
2
+ has_many :thingies, dependent: :destroy
3
+ end
@@ -0,0 +1,22 @@
1
+ class WidgetMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :widgets, :force => true do |t|
4
+ t.references :draft_parent
5
+ t.boolean "is_draft", :default => false
6
+ t.string :name
7
+ t.string :file
8
+ t.timestamps
9
+ end
10
+
11
+ create_table :thingies, :force => true do |t|
12
+ t.references :widget
13
+ t.string :name
14
+ t.timestamps
15
+ end
16
+ end
17
+
18
+ def self.down
19
+ drop_table :widgets
20
+ drop_table :thingies
21
+ end
22
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: conscript
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Steve Lorek
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-07-05 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: &70164099503280 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.3.5
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70164099503280
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &70164099502860 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70164099502860
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &70164099502400 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70164099502400
47
+ - !ruby/object:Gem::Dependency
48
+ name: sqlite3
49
+ requirement: &70164099501940 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70164099501940
58
+ - !ruby/object:Gem::Dependency
59
+ name: activerecord
60
+ requirement: &70164099501420 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: 3.2.13
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *70164099501420
69
+ - !ruby/object:Gem::Dependency
70
+ name: deep_cloneable
71
+ requirement: &70164099500900 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: 1.5.2
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: *70164099500900
80
+ description: Provides ActiveRecord models with draft instances, including associations
81
+ email:
82
+ - steve@stevelorek.com
83
+ executables: []
84
+ extensions: []
85
+ extra_rdoc_files: []
86
+ files:
87
+ - .gitignore
88
+ - .rspec
89
+ - Gemfile
90
+ - LICENSE.txt
91
+ - README.md
92
+ - Rakefile
93
+ - conscript.gemspec
94
+ - lib/conscript.rb
95
+ - lib/conscript/exception/already_draft.rb
96
+ - lib/conscript/exception/not_a_draft.rb
97
+ - lib/conscript/orm/activerecord.rb
98
+ - lib/conscript/version.rb
99
+ - spec/conscript/orm/activerecord_spec.rb
100
+ - spec/spec_helper.rb
101
+ - spec/support/thingy.rb
102
+ - spec/support/widget.rb
103
+ - spec/support/widget_migration.rb
104
+ homepage: http://github.com/slorek/conscript
105
+ licenses:
106
+ - MIT
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ! '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ none: false
119
+ requirements:
120
+ - - ! '>='
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 1.8.10
126
+ signing_key:
127
+ specification_version: 3
128
+ summary: Provides ActiveRecord models with draft instances, including associations
129
+ test_files:
130
+ - spec/conscript/orm/activerecord_spec.rb
131
+ - spec/spec_helper.rb
132
+ - spec/support/thingy.rb
133
+ - spec/support/widget.rb
134
+ - spec/support/widget_migration.rb