has_draft 1.4.0 → 2.0.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 +5 -5
- data/CHANGELOG.md +29 -0
- data/README.md +213 -0
- data/lib/has_draft/version.rb +1 -1
- data/lib/has_draft.rb +54 -43
- metadata +15 -126
- data/Appraisals +0 -23
- data/Gemfile +0 -3
- data/Gemfile.lock +0 -64
- data/README.rdoc +0 -227
- data/Rakefile +0 -30
- data/init.rb +0 -3
- data/lib/has_draft/railtie.rb +0 -19
- data/spec/db/database.yml +0 -21
- data/spec/db/schema.rb +0 -32
- data/spec/db/test.sqlite3 +0 -0
- data/spec/factories/article.rb +0 -20
- data/spec/has_draft_spec.rb +0 -111
- data/spec/spec_helper.rb +0 -34
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 29d15db9d31975799a93a854e620a005c6358befe684fdb7e1d5766324463d33
|
|
4
|
+
data.tar.gz: 85e9962ebe1ad2d7f76afe2a5b2be0f94cc2fb26d188ea746d0eed6330e66cd2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c20a9d20993e59c7161b1225c99b27c03341c7efb11762f34ef33a0cbdab616ce8a138f45eb97a415ca3b46e72aa624266e6af679abf25ff2be166dcded0f4c2
|
|
7
|
+
data.tar.gz: 9e4739990706a7fc0bb6500ec0a2caab469cca88550be61ab050f70effc6d611e20e67e41fc2e678fcd3e3601f4a93f413123f20b8178a36806c314148bbaca2
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
Supports Rails (ActiveRecord) 6.1 through 8.1 on Ruby 3.0 and later.
|
|
6
|
+
|
|
7
|
+
### Breaking changes
|
|
8
|
+
|
|
9
|
+
- Requires ActiveRecord 6.1+ and Ruby 3.0+.
|
|
10
|
+
- `instantiate_draft` builds the draft without saving it, as originally intended.
|
|
11
|
+
`instantiate_draft!` still saves it.
|
|
12
|
+
- On a new record, `instantiate_draft!` no longer saves the record. The draft is saved along with the
|
|
13
|
+
record instead. `save_to_draft` raises `ActiveRecord::RecordNotSaved`.
|
|
14
|
+
- The foreign key defaults to `"#{belongs_to}_id"`, so with `belongs_to: :element` it's
|
|
15
|
+
`element_id` rather than one named after the including class. This is unchanged when `:belongs_to`
|
|
16
|
+
isn't given.
|
|
17
|
+
- The Railtie and the `init.rb` files are gone. The gem hooks into ActiveRecord when it's required.
|
|
18
|
+
|
|
19
|
+
### Fixes
|
|
20
|
+
|
|
21
|
+
- `save_to_draft`, `destroy_draft!` and the `with_draft`/`without_draft` scopes work on modern Rails.
|
|
22
|
+
- `save_to_draft` returns false when the draft fails to save.
|
|
23
|
+
- Defining a model no longer needs a database connection. Drafted columns are worked out on first use.
|
|
24
|
+
- Columns that only exist on the draft table are no longer copied, which used to raise `NoMethodError`.
|
|
25
|
+
- A symbol `:foreign_key` is no longer copied as if it were a drafted column.
|
|
26
|
+
|
|
27
|
+
## 1.4.0 and earlier
|
|
28
|
+
|
|
29
|
+
See the [commit history](https://github.com/eigenben/has_draft/commits/master).
|
data/README.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# has_draft
|
|
2
|
+
|
|
3
|
+
[](https://github.com/eigenben/has_draft/actions/workflows/ci.yml)
|
|
4
|
+
[](https://rubygems.org/gems/has_draft)
|
|
5
|
+
|
|
6
|
+
Gives your ActiveRecord models a draft copy that lives alongside the live record. Typical uses:
|
|
7
|
+
|
|
8
|
+
- A draft and a live version of a page, where edits are previewed before they're published
|
|
9
|
+
- An approval workflow, where the live copy stays active while a draft waits for review
|
|
10
|
+
|
|
11
|
+
Drafts are stored in a separate, nearly identical table, joined to the live record by a `has_one`.
|
|
12
|
+
Your existing table and queries don't change: `Page.all` still returns only live pages, with no extra
|
|
13
|
+
conditions needed. The draft table is backed by a model that `has_draft` creates for you as a constant
|
|
14
|
+
on the original class, so if `Page` has a draft, `Page::Draft` is the model for the `page_drafts` table.
|
|
15
|
+
|
|
16
|
+
Because a draft is a real model with real columns, it can have its own validations, associations and
|
|
17
|
+
indexes, and you can query drafts with SQL like any other table. The trade-off is that the draft table
|
|
18
|
+
has to be kept in step with the live table: when you add a column to one, add it to the other.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
Requires Rails (ActiveRecord) 6.1 or later and Ruby 3.0 or later.
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
gem "has_draft"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Basic example
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
class CreateArticles < ActiveRecord::Migration[7.1]
|
|
32
|
+
def change
|
|
33
|
+
[:articles, :article_drafts].each do |table_name|
|
|
34
|
+
create_table table_name do |t|
|
|
35
|
+
t.references :article if table_name == :article_drafts
|
|
36
|
+
|
|
37
|
+
t.string :title
|
|
38
|
+
t.text :summary
|
|
39
|
+
t.text :body
|
|
40
|
+
t.date :post_date
|
|
41
|
+
t.timestamps
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class Article < ApplicationRecord
|
|
48
|
+
has_draft
|
|
49
|
+
end
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Class methods and scopes:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
Article.draft_class # => Article::Draft
|
|
56
|
+
Article.with_draft # articles that have a draft
|
|
57
|
+
Article.without_draft # articles that don't
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Working with drafts:
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
article = Article.create!(title: "My Title", body: "Full body", post_date: Date.today)
|
|
64
|
+
|
|
65
|
+
article.has_draft? # => false
|
|
66
|
+
article.instantiate_draft! # copies the article into a new, saved draft
|
|
67
|
+
article.has_draft? # => true
|
|
68
|
+
article.draft # => #<Article::Draft ...>
|
|
69
|
+
|
|
70
|
+
article.draft.update!(title: "New Title")
|
|
71
|
+
article.replace_with_draft! # copies the draft onto the article and saves it
|
|
72
|
+
article.title # => "New Title"
|
|
73
|
+
|
|
74
|
+
article.destroy_draft!
|
|
75
|
+
article.has_draft? # => false
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`save_to_draft` saves a record's unsaved changes into its draft (creating the draft if needed) instead
|
|
79
|
+
of the live table, then reloads the live record:
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
article.title = "Work in progress"
|
|
83
|
+
article.save_to_draft # validates the article first; returns false if invalid
|
|
84
|
+
article.title # => "New Title"
|
|
85
|
+
article.draft.title # => "Work in progress"
|
|
86
|
+
|
|
87
|
+
article.save_to_draft(false) # skip validation
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Drafts belong to a saved record. On a new record, `instantiate_draft!` builds the draft and it's saved
|
|
91
|
+
along with the record, while `save_to_draft` raises `ActiveRecord::RecordNotSaved`.
|
|
92
|
+
`instantiate_draft` (without the bang) builds the draft without saving it.
|
|
93
|
+
|
|
94
|
+
Every column the two tables share is copied, except the primary key, the foreign key, `created_at`,
|
|
95
|
+
`updated_at` and the STI type column. Columns that only exist on the draft table (say, an `approved_at`
|
|
96
|
+
for a review workflow) are left alone. `Article.draft_columns` lists the columns that are copied.
|
|
97
|
+
|
|
98
|
+
## Custom options
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
class Article < ApplicationRecord
|
|
102
|
+
has_draft class_name: "Copy", foreign_key: :news_article_id, table_name: "article_copies"
|
|
103
|
+
end
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
| Option | Default |
|
|
107
|
+
| -------------- | ---------------------------------------- |
|
|
108
|
+
| `:class_name` | `"Draft"` (defined as `Article::Draft`) |
|
|
109
|
+
| `:table_name` | `"article_drafts"` |
|
|
110
|
+
| `:belongs_to` | `:article`, the draft's parent association |
|
|
111
|
+
| `:foreign_key` | `"#{belongs_to}_id"`, so `"article_id"` |
|
|
112
|
+
| `:extends` | `ActiveRecord::Base`, the draft class's superclass |
|
|
113
|
+
|
|
114
|
+
## Single table inheritance
|
|
115
|
+
|
|
116
|
+
Subclasses draft into the base class's draft table. Use `:belongs_to` so every draft class shares the
|
|
117
|
+
same parent association, and `:extends` to share behavior between draft classes:
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
class CreateElements < ActiveRecord::Migration[7.1]
|
|
121
|
+
def change
|
|
122
|
+
[:elements, :element_drafts].each do |table_name|
|
|
123
|
+
create_table table_name do |t|
|
|
124
|
+
t.references :element if table_name == :element_drafts
|
|
125
|
+
|
|
126
|
+
t.string :title
|
|
127
|
+
t.text :content
|
|
128
|
+
t.string :type
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
class Element < ApplicationRecord
|
|
135
|
+
# Uses the element_drafts table, with each subclass's draft class stored via STI
|
|
136
|
+
class Draft < ApplicationRecord
|
|
137
|
+
def icon_path
|
|
138
|
+
"images/default.png"
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
class BlueElement < Element
|
|
144
|
+
has_draft belongs_to: :element, extends: Element::Draft do
|
|
145
|
+
def icon_path
|
|
146
|
+
"images/blue.png"
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
class UnknownElement < Element
|
|
152
|
+
has_draft belongs_to: :element, extends: Element::Draft
|
|
153
|
+
end
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Now `draft.element` works on any element's draft, without needing to know to call
|
|
157
|
+
`draft.blue_element` or `draft.unknown_element`. Calling `has_draft` in a class whose parent already
|
|
158
|
+
called it does nothing.
|
|
159
|
+
|
|
160
|
+
## Callbacks
|
|
161
|
+
|
|
162
|
+
Define any of these methods to hook into the draft lifecycle:
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
class Article < ApplicationRecord
|
|
166
|
+
has_draft
|
|
167
|
+
|
|
168
|
+
def before_instantiate_draft
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def before_replace_with_draft
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def before_destroy_draft
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Extending the draft class
|
|
180
|
+
|
|
181
|
+
Since you don't define the draft class yourself, pass a block to `has_draft` to run code in its context:
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
class Article < ApplicationRecord
|
|
185
|
+
belongs_to :user
|
|
186
|
+
|
|
187
|
+
has_draft do
|
|
188
|
+
belongs_to :last_updated_user, class_name: "User", optional: true
|
|
189
|
+
|
|
190
|
+
def approve!
|
|
191
|
+
update!(approved_at: Time.current)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Development
|
|
198
|
+
|
|
199
|
+
```sh
|
|
200
|
+
bundle install
|
|
201
|
+
bundle exec rake
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
To test against a specific Rails version, use one of the gemfiles in `gemfiles/`:
|
|
205
|
+
|
|
206
|
+
```sh
|
|
207
|
+
BUNDLE_GEMFILE=gemfiles/rails_6.1.gemfile bundle install
|
|
208
|
+
BUNDLE_GEMFILE=gemfiles/rails_6.1.gemfile bundle exec rake
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## License
|
|
212
|
+
|
|
213
|
+
MIT
|
data/lib/has_draft/version.rb
CHANGED
data/lib/has_draft.rb
CHANGED
|
@@ -1,31 +1,35 @@
|
|
|
1
|
+
require "active_support"
|
|
2
|
+
require "active_record"
|
|
3
|
+
require "has_draft/version"
|
|
4
|
+
|
|
1
5
|
module HasDraft
|
|
2
6
|
def self.included(base)
|
|
3
7
|
base.extend ClassMethods
|
|
4
8
|
end
|
|
5
|
-
|
|
9
|
+
|
|
6
10
|
module ClassMethods
|
|
7
11
|
def has_draft(options = {}, &block)
|
|
8
12
|
return if self.included_modules.include?(HasDraft::InstanceMethods)
|
|
9
13
|
include HasDraft::InstanceMethods
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
self.draft_class_name = options[:class_name] || 'Draft'
|
|
14
|
-
self.draft_foreign_key = options[:foreign_key] || self.to_s.foreign_key
|
|
15
|
-
self.draft_table_name = options[:table_name] || "#{table_name_prefix}#{base_class.name.demodulize.underscore}_drafts#{table_name_suffix}"
|
|
14
|
+
|
|
15
|
+
class_attribute :draft_class_name, :draft_foreign_key, :draft_table_name, :instance_writer => false
|
|
16
16
|
|
|
17
17
|
# Default parent association
|
|
18
|
-
options[:belongs_to] = self.
|
|
18
|
+
options[:belongs_to] = self.name.demodulize.underscore.to_sym if options[:belongs_to].nil?
|
|
19
|
+
|
|
20
|
+
self.draft_class_name = options[:class_name] || 'Draft'
|
|
21
|
+
self.draft_foreign_key = (options[:foreign_key] || "#{options[:belongs_to]}_id").to_s
|
|
22
|
+
self.draft_table_name = options[:table_name] || "#{table_name_prefix}#{base_class.name.demodulize.underscore}_drafts#{table_name_suffix}"
|
|
19
23
|
|
|
20
24
|
# Create Relationship to Draft Copy
|
|
21
25
|
class_eval do
|
|
22
|
-
has_one :draft, :class_name => "#{self.
|
|
26
|
+
has_one :draft, :class_name => "#{self.name}::#{draft_class_name}",
|
|
23
27
|
:foreign_key => draft_foreign_key,
|
|
24
28
|
:dependent => :destroy,
|
|
25
29
|
:inverse_of => options[:belongs_to]
|
|
26
30
|
|
|
27
|
-
scope :with_draft, lambda { includes(:draft).where(
|
|
28
|
-
scope :without_draft, lambda { includes(:draft).where(
|
|
31
|
+
scope :with_draft, lambda { includes(:draft).where.not(draft_table_name => { draft_class.primary_key => nil }) }
|
|
32
|
+
scope :without_draft, lambda { includes(:draft).where(draft_table_name => { draft_class.primary_key => nil }) }
|
|
29
33
|
end
|
|
30
34
|
|
|
31
35
|
# Default parent class to ActiveRecord::Base
|
|
@@ -33,95 +37,102 @@ module HasDraft
|
|
|
33
37
|
|
|
34
38
|
# Dynamically Create Model::Draft Class
|
|
35
39
|
const_set(draft_class_name, Class.new(options[:extends]))
|
|
36
|
-
|
|
37
|
-
draft_class.
|
|
40
|
+
|
|
41
|
+
draft_class.class_attribute :original_class, :instance_writer => false
|
|
38
42
|
draft_class.original_class = self
|
|
39
43
|
draft_class.table_name = draft_table_name
|
|
40
44
|
|
|
41
45
|
# Draft Parent Association
|
|
42
|
-
draft_class.belongs_to options[:belongs_to], :class_name => "::#{self.
|
|
46
|
+
draft_class.belongs_to options[:belongs_to], :class_name => "::#{self.name}", :foreign_key => draft_foreign_key, :inverse_of => :draft
|
|
43
47
|
|
|
44
48
|
# Block extension
|
|
45
49
|
draft_class.class_eval(&block) if block_given?
|
|
46
|
-
|
|
47
|
-
# Finally setup which columns to draft
|
|
48
|
-
self.draft_columns = draft_class.new.attributes.keys - [draft_class.primary_key, draft_foreign_key, 'created_at', 'updated_at', inheritance_column]
|
|
49
50
|
end
|
|
50
|
-
|
|
51
|
+
|
|
52
|
+
# Columns copied between the live record and its draft: those the two share, minus keys and
|
|
53
|
+
# timestamps. Computed on first use so defining the model doesn't need a database connection.
|
|
54
|
+
def draft_columns
|
|
55
|
+
@draft_columns ||= (draft_class.attribute_types.keys & attribute_types.keys) - [draft_class.primary_key, draft_foreign_key, 'created_at', 'updated_at', inheritance_column]
|
|
56
|
+
end
|
|
57
|
+
|
|
51
58
|
def draft_class
|
|
52
59
|
const_get(draft_class_name)
|
|
53
60
|
end
|
|
54
61
|
end
|
|
55
|
-
|
|
62
|
+
|
|
56
63
|
module InstanceMethods
|
|
57
64
|
def has_draft?
|
|
58
65
|
!self.draft.nil?
|
|
59
66
|
end
|
|
60
|
-
|
|
67
|
+
|
|
61
68
|
def save_to_draft(perform_validation = true)
|
|
69
|
+
raise ActiveRecord::RecordNotSaved.new("Can't save a draft of a new record; save the record first", self) if self.new_record?
|
|
62
70
|
return false if perform_validation and !self.valid?
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
self.draft.save(perform_validation)
|
|
71
|
+
|
|
72
|
+
has_draft? ? copy_attributes_to_draft : instantiate_draft
|
|
73
|
+
|
|
74
|
+
return false unless self.draft.save(:validate => perform_validation)
|
|
68
75
|
self.reload
|
|
69
76
|
end
|
|
70
|
-
|
|
77
|
+
|
|
78
|
+
# Builds a draft copy of the record without saving it.
|
|
71
79
|
def instantiate_draft
|
|
72
80
|
self.build_draft
|
|
73
|
-
|
|
81
|
+
|
|
74
82
|
copy_attributes_to_draft
|
|
75
83
|
before_instantiate_draft
|
|
76
|
-
|
|
77
|
-
self.draft
|
|
84
|
+
|
|
85
|
+
self.draft
|
|
78
86
|
end
|
|
79
|
-
|
|
87
|
+
|
|
88
|
+
# Builds and saves a draft copy of the record. On a new record, the draft is saved along with it.
|
|
80
89
|
def instantiate_draft!
|
|
81
|
-
instantiate_draft do |draft|
|
|
90
|
+
instantiate_draft.tap do |draft|
|
|
82
91
|
draft.save unless self.new_record?
|
|
83
92
|
end
|
|
84
93
|
end
|
|
85
|
-
|
|
94
|
+
|
|
86
95
|
def copy_attributes_to_draft
|
|
87
96
|
self.class.draft_columns.each do |attribute|
|
|
88
97
|
self.draft.send("#{attribute}=", send(attribute))
|
|
89
98
|
end
|
|
90
99
|
self
|
|
91
100
|
end
|
|
92
|
-
|
|
101
|
+
|
|
93
102
|
def copy_attributes_from_draft
|
|
94
103
|
self.class.draft_columns.each do |attribute|
|
|
95
104
|
self.send("#{attribute}=", self.draft.send(attribute))
|
|
96
105
|
end
|
|
97
106
|
self
|
|
98
107
|
end
|
|
99
|
-
|
|
108
|
+
|
|
100
109
|
def before_instantiate_draft
|
|
101
110
|
end
|
|
102
|
-
|
|
111
|
+
|
|
103
112
|
def replace_with_draft!
|
|
104
113
|
copy_attributes_from_draft
|
|
105
|
-
|
|
114
|
+
|
|
106
115
|
before_replace_with_draft
|
|
107
|
-
|
|
116
|
+
|
|
108
117
|
self.save unless self.new_record?
|
|
109
118
|
self
|
|
110
119
|
end
|
|
111
|
-
|
|
120
|
+
|
|
112
121
|
def before_replace_with_draft
|
|
113
122
|
end
|
|
114
|
-
|
|
123
|
+
|
|
115
124
|
def destroy_draft!
|
|
116
125
|
before_destroy_draft
|
|
117
|
-
|
|
126
|
+
|
|
118
127
|
self.draft.destroy if self.draft
|
|
119
|
-
self.
|
|
128
|
+
self.reload_draft
|
|
120
129
|
end
|
|
121
|
-
|
|
130
|
+
|
|
122
131
|
def before_destroy_draft
|
|
123
132
|
end
|
|
124
133
|
end
|
|
125
134
|
end
|
|
126
135
|
|
|
127
|
-
|
|
136
|
+
ActiveSupport.on_load(:active_record) do
|
|
137
|
+
include HasDraft
|
|
138
|
+
end
|
metadata
CHANGED
|
@@ -1,127 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: has_draft
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ben Hughes
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: activesupport
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - ">="
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: 3.0.0
|
|
20
|
-
type: :runtime
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - ">="
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: 3.0.0
|
|
27
12
|
- !ruby/object:Gem::Dependency
|
|
28
13
|
name: activerecord
|
|
29
14
|
requirement: !ruby/object:Gem::Requirement
|
|
30
15
|
requirements:
|
|
31
16
|
- - ">="
|
|
32
17
|
- !ruby/object:Gem::Version
|
|
33
|
-
version:
|
|
18
|
+
version: '6.1'
|
|
34
19
|
type: :runtime
|
|
35
20
|
prerelease: false
|
|
36
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
22
|
requirements:
|
|
38
23
|
- - ">="
|
|
39
24
|
- !ruby/object:Gem::Version
|
|
40
|
-
version:
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: rspec
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - "~>"
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '2.0'
|
|
48
|
-
type: :development
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - "~>"
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '2.0'
|
|
55
|
-
- !ruby/object:Gem::Dependency
|
|
56
|
-
name: factory_girl
|
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
|
58
|
-
requirements:
|
|
59
|
-
- - "~>"
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: 1.3.1
|
|
62
|
-
type: :development
|
|
63
|
-
prerelease: false
|
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - "~>"
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: 1.3.1
|
|
69
|
-
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: faker
|
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
|
72
|
-
requirements:
|
|
73
|
-
- - "~>"
|
|
74
|
-
- !ruby/object:Gem::Version
|
|
75
|
-
version: 0.9.1
|
|
76
|
-
type: :development
|
|
77
|
-
prerelease: false
|
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
-
requirements:
|
|
80
|
-
- - "~>"
|
|
81
|
-
- !ruby/object:Gem::Version
|
|
82
|
-
version: 0.9.1
|
|
83
|
-
- !ruby/object:Gem::Dependency
|
|
84
|
-
name: sqlite3
|
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
|
86
|
-
requirements:
|
|
87
|
-
- - "~>"
|
|
88
|
-
- !ruby/object:Gem::Version
|
|
89
|
-
version: 1.3.4
|
|
90
|
-
type: :development
|
|
91
|
-
prerelease: false
|
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
-
requirements:
|
|
94
|
-
- - "~>"
|
|
95
|
-
- !ruby/object:Gem::Version
|
|
96
|
-
version: 1.3.4
|
|
97
|
-
- !ruby/object:Gem::Dependency
|
|
98
|
-
name: appraisal
|
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
|
100
|
-
requirements:
|
|
101
|
-
- - "~>"
|
|
102
|
-
- !ruby/object:Gem::Version
|
|
103
|
-
version: 0.4.0
|
|
104
|
-
type: :development
|
|
105
|
-
prerelease: false
|
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
-
requirements:
|
|
108
|
-
- - "~>"
|
|
109
|
-
- !ruby/object:Gem::Version
|
|
110
|
-
version: 0.4.0
|
|
111
|
-
- !ruby/object:Gem::Dependency
|
|
112
|
-
name: rake
|
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
|
114
|
-
requirements:
|
|
115
|
-
- - "~>"
|
|
116
|
-
- !ruby/object:Gem::Version
|
|
117
|
-
version: 10.1.0
|
|
118
|
-
type: :development
|
|
119
|
-
prerelease: false
|
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
-
requirements:
|
|
122
|
-
- - "~>"
|
|
123
|
-
- !ruby/object:Gem::Version
|
|
124
|
-
version: 10.1.0
|
|
25
|
+
version: '6.1'
|
|
125
26
|
description: Allows for your ActiveRecord models to have drafts which are stored in
|
|
126
27
|
a separate duplicate table.
|
|
127
28
|
email: ben@railsgarden.com
|
|
@@ -129,27 +30,18 @@ executables: []
|
|
|
129
30
|
extensions: []
|
|
130
31
|
extra_rdoc_files: []
|
|
131
32
|
files:
|
|
132
|
-
-
|
|
133
|
-
- Gemfile
|
|
134
|
-
- Gemfile.lock
|
|
33
|
+
- CHANGELOG.md
|
|
135
34
|
- LICENSE
|
|
136
|
-
- README.
|
|
137
|
-
- Rakefile
|
|
138
|
-
- init.rb
|
|
35
|
+
- README.md
|
|
139
36
|
- lib/has_draft.rb
|
|
140
|
-
- lib/has_draft/railtie.rb
|
|
141
37
|
- lib/has_draft/version.rb
|
|
142
|
-
|
|
143
|
-
- spec/db/schema.rb
|
|
144
|
-
- spec/db/test.sqlite3
|
|
145
|
-
- spec/factories/article.rb
|
|
146
|
-
- spec/has_draft_spec.rb
|
|
147
|
-
- spec/spec_helper.rb
|
|
148
|
-
homepage: http://github.com/rubiety/has_draft
|
|
38
|
+
homepage: https://github.com/eigenben/has_draft
|
|
149
39
|
licenses:
|
|
150
40
|
- MIT
|
|
151
|
-
metadata:
|
|
152
|
-
|
|
41
|
+
metadata:
|
|
42
|
+
source_code_uri: https://github.com/eigenben/has_draft
|
|
43
|
+
changelog_uri: https://github.com/eigenben/has_draft/blob/master/CHANGELOG.md
|
|
44
|
+
bug_tracker_uri: https://github.com/eigenben/has_draft/issues
|
|
153
45
|
rdoc_options: []
|
|
154
46
|
require_paths:
|
|
155
47
|
- lib
|
|
@@ -157,17 +49,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
157
49
|
requirements:
|
|
158
50
|
- - ">="
|
|
159
51
|
- !ruby/object:Gem::Version
|
|
160
|
-
version: '0'
|
|
52
|
+
version: '3.0'
|
|
161
53
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
162
54
|
requirements:
|
|
163
55
|
- - ">="
|
|
164
56
|
- !ruby/object:Gem::Version
|
|
165
|
-
version:
|
|
57
|
+
version: '0'
|
|
166
58
|
requirements: []
|
|
167
|
-
|
|
168
|
-
rubygems_version: 2.5.1
|
|
169
|
-
signing_key:
|
|
59
|
+
rubygems_version: 3.6.7
|
|
170
60
|
specification_version: 4
|
|
171
|
-
summary:
|
|
61
|
+
summary: Attach drafts to your ActiveRecord models.
|
|
172
62
|
test_files: []
|
|
173
|
-
has_rdoc:
|
data/Appraisals
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
appraise "3.0" do
|
|
2
|
-
gem "rails", "~> 3.0.12"
|
|
3
|
-
end
|
|
4
|
-
|
|
5
|
-
appraise "3.1" do
|
|
6
|
-
gem "rails", "~> 3.1.4"
|
|
7
|
-
gem "i18n", "~> 0.6.0"
|
|
8
|
-
gem "rack-cache", "~> 1.2.0"
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
appraise "3.2" do
|
|
12
|
-
gem "rails", "~> 3.2.2"
|
|
13
|
-
gem "i18n", "~> 0.6.0"
|
|
14
|
-
gem "rack-cache", "~> 1.2.0"
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
appraise "4.0" do
|
|
18
|
-
gem "rails", "~> 4.0.0"
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
appraise "5.0" do
|
|
22
|
-
gem "rails", "~> 5.0.0"
|
|
23
|
-
end
|
data/Gemfile
DELETED
data/Gemfile.lock
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
PATH
|
|
2
|
-
remote: .
|
|
3
|
-
specs:
|
|
4
|
-
has_draft (1.4.0)
|
|
5
|
-
activerecord (>= 3.0.0)
|
|
6
|
-
activesupport (>= 3.0.0)
|
|
7
|
-
|
|
8
|
-
GEM
|
|
9
|
-
remote: https://rubygems.org/
|
|
10
|
-
specs:
|
|
11
|
-
activemodel (4.0.3)
|
|
12
|
-
activesupport (= 4.0.3)
|
|
13
|
-
builder (~> 3.1.0)
|
|
14
|
-
activerecord (4.0.3)
|
|
15
|
-
activemodel (= 4.0.3)
|
|
16
|
-
activerecord-deprecated_finders (~> 1.0.2)
|
|
17
|
-
activesupport (= 4.0.3)
|
|
18
|
-
arel (~> 4.0.0)
|
|
19
|
-
activerecord-deprecated_finders (1.0.4)
|
|
20
|
-
activesupport (4.0.3)
|
|
21
|
-
i18n (~> 0.6, >= 0.6.4)
|
|
22
|
-
minitest (~> 4.2)
|
|
23
|
-
multi_json (~> 1.3)
|
|
24
|
-
thread_safe (~> 0.1)
|
|
25
|
-
tzinfo (~> 0.3.37)
|
|
26
|
-
appraisal (0.4.1)
|
|
27
|
-
bundler
|
|
28
|
-
rake
|
|
29
|
-
arel (4.0.2)
|
|
30
|
-
builder (3.1.4)
|
|
31
|
-
diff-lcs (1.2.4)
|
|
32
|
-
factory_girl (1.3.3)
|
|
33
|
-
faker (0.9.5)
|
|
34
|
-
i18n (~> 0.4)
|
|
35
|
-
i18n (0.6.5)
|
|
36
|
-
minitest (4.7.5)
|
|
37
|
-
multi_json (1.13.1)
|
|
38
|
-
rake (10.1.0)
|
|
39
|
-
rspec (2.14.1)
|
|
40
|
-
rspec-core (~> 2.14.0)
|
|
41
|
-
rspec-expectations (~> 2.14.0)
|
|
42
|
-
rspec-mocks (~> 2.14.0)
|
|
43
|
-
rspec-core (2.14.5)
|
|
44
|
-
rspec-expectations (2.14.3)
|
|
45
|
-
diff-lcs (>= 1.1.3, < 2.0)
|
|
46
|
-
rspec-mocks (2.14.3)
|
|
47
|
-
sqlite3 (1.3.13)
|
|
48
|
-
thread_safe (0.3.6)
|
|
49
|
-
tzinfo (0.3.54)
|
|
50
|
-
|
|
51
|
-
PLATFORMS
|
|
52
|
-
ruby
|
|
53
|
-
|
|
54
|
-
DEPENDENCIES
|
|
55
|
-
appraisal (~> 0.4.0)
|
|
56
|
-
factory_girl (~> 1.3.1)
|
|
57
|
-
faker (~> 0.9.1)
|
|
58
|
-
has_draft!
|
|
59
|
-
rake (~> 10.1.0)
|
|
60
|
-
rspec (~> 2.0)
|
|
61
|
-
sqlite3 (~> 1.3.4)
|
|
62
|
-
|
|
63
|
-
BUNDLED WITH
|
|
64
|
-
1.17.1
|
data/README.rdoc
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
{<img src="https://secure.travis-ci.org/rubiety/has_draft.svg?branch=master" alt="Build Status" />}[http://travis-ci.org/rubiety/has_draft]
|
|
2
|
-
{<img src="https://gemnasium.com/rubiety/has_draft.svg" alt="Dependency Status" />}[https://gemnasium.com/rubiety/has_draft]
|
|
3
|
-
{<img src="https://codeclimate.com/github/rubiety/has_draft.svg" />}[https://codeclimate.com/github/rubiety/has_draft]
|
|
4
|
-
|
|
5
|
-
== Has Draft
|
|
6
|
-
|
|
7
|
-
Allows for multiple "drafts" of a model which can be useful when developing:
|
|
8
|
-
* Draft/Live Version of Pages, for examples
|
|
9
|
-
* A workflow system whereby a live copy may need to be active while a draft copy is
|
|
10
|
-
awaiting approval.
|
|
11
|
-
|
|
12
|
-
This was built to be able to be tacked on to existing models, so the data schema doesn't need to change
|
|
13
|
-
at all for the model this is applied to. Drafts are actually stored in a nearly-identical table
|
|
14
|
-
and there is a has_one relationship to this. This separation allows the base model to really be treated
|
|
15
|
-
just as before without having to apply conditions in queries to make sure you are really getting the
|
|
16
|
-
"live" (non-draft) copy: Page.all will still only return the non-draft pages. This separate table is backed by
|
|
17
|
-
a model created on the fly as a constant on the original model class. For example if a Page has_draft,
|
|
18
|
-
a Page::Draft class will exist as the model for the page_drafts table.
|
|
19
|
-
|
|
20
|
-
== Installation
|
|
21
|
-
|
|
22
|
-
This gem supports ActiveRecord 3, 4, and 5.
|
|
23
|
-
|
|
24
|
-
In your Gemfile:
|
|
25
|
-
|
|
26
|
-
gem "has_draft"
|
|
27
|
-
|
|
28
|
-
== Basic Example
|
|
29
|
-
|
|
30
|
-
## First Migration (If Creating base model and drafts at the same time):
|
|
31
|
-
class InitialSchema < ActiveRecord::Migration
|
|
32
|
-
|
|
33
|
-
[:articles, :article_drafts].each do |table_name|
|
|
34
|
-
create_table table_name, :force => true do |t|
|
|
35
|
-
t.references :article if table_name == :article_drafts
|
|
36
|
-
|
|
37
|
-
t.string :title
|
|
38
|
-
t.text :summary
|
|
39
|
-
t.text :body
|
|
40
|
-
t.date :post_date
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
## Model Class
|
|
48
|
-
class Article < ActiveRecord::Base
|
|
49
|
-
has_draft
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
## Exposed Class Methods & Scopes:
|
|
53
|
-
Article.draft_class
|
|
54
|
-
=> Article::Draft
|
|
55
|
-
Article.with_draft.all
|
|
56
|
-
=> (Articles that have an associated draft)
|
|
57
|
-
Article.without_draft.all
|
|
58
|
-
=> (Articles with no associated draft)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
## Usage Examples:
|
|
63
|
-
|
|
64
|
-
article = Article.create(
|
|
65
|
-
:title => "My Title",
|
|
66
|
-
:summary => "Information here.",
|
|
67
|
-
:body => "Full body",
|
|
68
|
-
:post_date => Date.today
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
article.has_draft?
|
|
72
|
-
=> false
|
|
73
|
-
|
|
74
|
-
article.instantiate_draft!
|
|
75
|
-
|
|
76
|
-
article.has_draft?
|
|
77
|
-
=> true
|
|
78
|
-
|
|
79
|
-
article.draft
|
|
80
|
-
=> Article::Draft Instance
|
|
81
|
-
|
|
82
|
-
article.draft.update_attributes(
|
|
83
|
-
:title => "New Title"
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
article.replace_with_draft!
|
|
87
|
-
|
|
88
|
-
article.title
|
|
89
|
-
=> "New Title"
|
|
90
|
-
|
|
91
|
-
article.destroy_draft!
|
|
92
|
-
|
|
93
|
-
article.has_draft?
|
|
94
|
-
=> false
|
|
95
|
-
|
|
96
|
-
== Custom Options
|
|
97
|
-
|
|
98
|
-
## First Migration (If Creating base model and drafts at the same time):
|
|
99
|
-
class InitialSchema < ActiveRecord::Migration
|
|
100
|
-
|
|
101
|
-
[:articles, :article_copies].each do |table_name|
|
|
102
|
-
create_table table_name, :force => true do |t|
|
|
103
|
-
t.integer :news_article_id if table_name == :article_copies
|
|
104
|
-
|
|
105
|
-
t.string :title
|
|
106
|
-
t.text :summary
|
|
107
|
-
t.text :body
|
|
108
|
-
t.date :post_date
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
## Model Class
|
|
115
|
-
class Article < ActiveRecord::Base
|
|
116
|
-
has_draft :class_name => 'Copy', :foreign_key => :news_article_id, :table_name => 'article_copies'
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
## Single Table Inheritance Example
|
|
120
|
-
class InitialSchema < ActiveRecord::Migration
|
|
121
|
-
|
|
122
|
-
[:elements, :element_drafts].each do |table_name|
|
|
123
|
-
create_table table_name, :force => true do |t|
|
|
124
|
-
t.integer :element_id if table_name == :element_drafts
|
|
125
|
-
|
|
126
|
-
t.string :title
|
|
127
|
-
t.text :content
|
|
128
|
-
t.string :type
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
class Element < ActiveRecord::Base
|
|
135
|
-
class Draft < ActiveRecord::Base
|
|
136
|
-
def element_icon_path
|
|
137
|
-
'assets/images/default.png'
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
module HasDraftCallbacks
|
|
142
|
-
def before_instantiate_draft
|
|
143
|
-
# Need to append ::Draft so that it knows to use the Draft class
|
|
144
|
-
self.draft.type = self.type + '::Draft'
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def before_replace_with_draft
|
|
148
|
-
# We are storing drafts with the ::Draft on the end so we have to strip it
|
|
149
|
-
self.type = self.draft.type.split(':')[0]
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
class BlueElement < Element
|
|
155
|
-
has_draft :belongs_to => :element, :extends => Element::Draft do
|
|
156
|
-
def element_icon_path
|
|
157
|
-
'assets/images/blue.png'
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
include Element::HasDraftCallbacks
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
class UnknownElement < Element
|
|
165
|
-
has_draft :belongs_to => :element, :extends => Element::Draft
|
|
166
|
-
|
|
167
|
-
include Element::HasDraftCallbacks
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
This allows us to call element_draft.element on any subclass of Element::Draft
|
|
171
|
-
instead of needing to know to call element_draft.blue_element or element_draft.unknown_element.
|
|
172
|
-
|
|
173
|
-
Note: calling has_draft will have no effect if the parent class has already executed has_draft.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
== Method Callbacks
|
|
178
|
-
|
|
179
|
-
There are three callbacks you can specify directly as methods:
|
|
180
|
-
|
|
181
|
-
class Article < ActiveRecord::Base
|
|
182
|
-
has_draft
|
|
183
|
-
|
|
184
|
-
def before_instantiate_draft
|
|
185
|
-
# Do Something
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
def before_replace_with_draft
|
|
189
|
-
# Do Something
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
def before_destroy_draft
|
|
193
|
-
# Do Something
|
|
194
|
-
end
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
== Extending the Draft Class
|
|
200
|
-
|
|
201
|
-
Because you don't directly define the draft class, you can specify a block of code to be run in its
|
|
202
|
-
context by passing a block to has_draft:
|
|
203
|
-
|
|
204
|
-
class Article < ActiveRecord::Base
|
|
205
|
-
belongs_to :user
|
|
206
|
-
|
|
207
|
-
has_draft do
|
|
208
|
-
belongs_to :last_updated_user
|
|
209
|
-
|
|
210
|
-
def approve!
|
|
211
|
-
self.approved_at = Time.now
|
|
212
|
-
self.save
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
== Running Tests
|
|
219
|
-
|
|
220
|
-
This gem uses appraisal to test with different versions of the dependencies. See Appraisal first for which versions are tested.
|
|
221
|
-
|
|
222
|
-
# Just the gems locked in Gemfile.lock
|
|
223
|
-
$ bundle exec rake test
|
|
224
|
-
|
|
225
|
-
# All of the Appraisals:
|
|
226
|
-
$ bundle exec rake all
|
|
227
|
-
|
data/Rakefile
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
require 'rubygems'
|
|
2
|
-
require 'bundler/setup'
|
|
3
|
-
|
|
4
|
-
require 'rake'
|
|
5
|
-
require 'rspec/core/rake_task'
|
|
6
|
-
require 'appraisal'
|
|
7
|
-
|
|
8
|
-
Bundler::GemHelper.install_tasks
|
|
9
|
-
|
|
10
|
-
desc 'Default: run unit tests.'
|
|
11
|
-
task :default => [:clean, :all]
|
|
12
|
-
|
|
13
|
-
desc "Run Specs against all Appraisals"
|
|
14
|
-
task :all => :spec do
|
|
15
|
-
Rake::Task["appraisal:install"].execute
|
|
16
|
-
system("bundle exec rake -s appraisal spec")
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
desc "Run Specs"
|
|
20
|
-
RSpec::Core::RakeTask.new(:spec) do |t|
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
task :test => :spec
|
|
24
|
-
|
|
25
|
-
desc "Clean up files."
|
|
26
|
-
task :clean do |t|
|
|
27
|
-
FileUtils.rm_rf "tmp"
|
|
28
|
-
Dir.glob("spec/db/*.sqlite3").each {|f| FileUtils.rm f }
|
|
29
|
-
Dir.glob("has_draft-*.gem").each {|f| FileUtils.rm f }
|
|
30
|
-
end
|
data/init.rb
DELETED
data/lib/has_draft/railtie.rb
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
module HasDraft
|
|
2
|
-
if defined?(Rails::Railtie)
|
|
3
|
-
require "rails"
|
|
4
|
-
|
|
5
|
-
class Railtie < Rails::Railtie
|
|
6
|
-
initializer "has_draft.extend_active_record" do
|
|
7
|
-
ActiveSupport.on_load(:active_record) do
|
|
8
|
-
HasDraft::Railtie.insert
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
class Railtie
|
|
15
|
-
def self.insert
|
|
16
|
-
ActiveRecord::Base.send(:include, HasDraft)
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
data/spec/db/database.yml
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
sqlite:
|
|
2
|
-
adapter: sqlite
|
|
3
|
-
database: spec/db/test.sqlite
|
|
4
|
-
|
|
5
|
-
sqlite3:
|
|
6
|
-
adapter: sqlite3
|
|
7
|
-
database: spec/db/test.sqlite3
|
|
8
|
-
|
|
9
|
-
postgresql:
|
|
10
|
-
adapter: postgresql
|
|
11
|
-
username: postgres
|
|
12
|
-
password: postgres
|
|
13
|
-
database: has_draft_plugin_test
|
|
14
|
-
min_messages: ERROR
|
|
15
|
-
|
|
16
|
-
mysql:
|
|
17
|
-
adapter: mysql
|
|
18
|
-
host: localhost
|
|
19
|
-
username: root
|
|
20
|
-
password:
|
|
21
|
-
database: has_draft_plugin_test
|
data/spec/db/schema.rb
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
ActiveRecord::Schema.define(:version => 0) do
|
|
2
|
-
|
|
3
|
-
[:articles, :article_drafts].each do |table_name|
|
|
4
|
-
create_table table_name, :force => true do |t|
|
|
5
|
-
t.references :article if table_name == :article_drafts
|
|
6
|
-
|
|
7
|
-
t.string :title
|
|
8
|
-
t.text :summary
|
|
9
|
-
t.text :body
|
|
10
|
-
t.date :post_date
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
[:posts, :post_drafts].each do |table_name|
|
|
15
|
-
create_table table_name, :force => true do |t|
|
|
16
|
-
t.references :post if table_name == :post_drafts
|
|
17
|
-
|
|
18
|
-
t.string :title
|
|
19
|
-
t.text :body
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
[:pages, :page_drafts].each do |table_name|
|
|
24
|
-
create_table table_name, :force => true do |t|
|
|
25
|
-
t.references :page if table_name == :page_drafts
|
|
26
|
-
|
|
27
|
-
t.string :title
|
|
28
|
-
t.text :body
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
end
|
data/spec/db/test.sqlite3
DELETED
|
Binary file
|
data/spec/factories/article.rb
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
require "factory_girl"
|
|
2
|
-
|
|
3
|
-
Factory.define(:article) do |f|
|
|
4
|
-
f.title { Faker::Lorem.sentence }
|
|
5
|
-
f.summary { Faker::Lorem.paragraphs.first }
|
|
6
|
-
f.body { Faker::Lorem.paragraphs.join("\n\n") }
|
|
7
|
-
f.post_date { 1.day.ago.to_date }
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
Factory.define(:article_draft, :class => Article::Draft) do |f|
|
|
11
|
-
f.association(:article)
|
|
12
|
-
f.title { Faker::Lorem.sentence }
|
|
13
|
-
f.summary { Faker::Lorem.paragraphs.first }
|
|
14
|
-
f.body { Faker::Lorem.paragraphs.join("\n\n") }
|
|
15
|
-
f.post_date { 1.day.ago.to_date }
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
Factory.define(:article_with_draft, :parent => :article) do |f|
|
|
19
|
-
f.association(:draft, :factory => :article_draft)
|
|
20
|
-
end
|
data/spec/has_draft_spec.rb
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
require "spec_helper"
|
|
2
|
-
|
|
3
|
-
describe HasDraft do
|
|
4
|
-
before(:all) do
|
|
5
|
-
ActiveSupport::Deprecation.silenced = true
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
context "Model with has_draft" do
|
|
9
|
-
it "should expose #draft_class_name as Draft" do
|
|
10
|
-
Article.draft_class_name.should == "Draft"
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
it "should expose #draft_class as Article::Draft" do
|
|
14
|
-
Article.draft_class.should == Article::Draft
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
it "should default #draft_foreign_key as article_id" do
|
|
18
|
-
Article.draft_foreign_key.should == "article_id"
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
it "should default #draft_table_name as article_drafts" do
|
|
22
|
-
Article.draft_table_name.should == "article_drafts"
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
describe "Draft Model" do
|
|
26
|
-
it "should be defined under the Article namespace" do
|
|
27
|
-
Article.constants.map(&:to_s).should include('Draft')
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
it "should expose #original_class as Article" do
|
|
31
|
-
Article::Draft.original_class.should == Article
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
context "an article" do
|
|
37
|
-
before { @article = Factory.create(:article) }
|
|
38
|
-
|
|
39
|
-
context "when instantiating a new draft" do
|
|
40
|
-
before { @article.instantiate_draft! }
|
|
41
|
-
|
|
42
|
-
it "should create draft" do
|
|
43
|
-
@article.draft.should_not be_nil
|
|
44
|
-
@article.draft.should_not be_new_record
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
it "should copy draft fields" do
|
|
48
|
-
Article.draft_columns.each do |column|
|
|
49
|
-
@article.send(column).should == @article.draft.send(column)
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
it "should set the associations as inverses of each other" do
|
|
54
|
-
@article.draft.article.should be @article
|
|
55
|
-
article_draft = Article::Draft.find(@article.draft.id)
|
|
56
|
-
article_draft.article.draft.should be article_draft
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
context "when destroying an existing draft" do
|
|
61
|
-
before do
|
|
62
|
-
@article = Factory.create(:article_with_draft)
|
|
63
|
-
@article.destroy_draft!
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
it "should associated draft" do
|
|
67
|
-
@article.draft.should be_nil
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
context "when replacing with draft" do
|
|
72
|
-
before do
|
|
73
|
-
@article = Factory.create(:article_with_draft)
|
|
74
|
-
@article.replace_with_draft!
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
it "should still have draft" do
|
|
78
|
-
@article.draft.should_not be_nil
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
it "should now have the same field values as draft" do
|
|
82
|
-
Article.draft_columns.each do |column|
|
|
83
|
-
@article.send(column).should == @article.draft.send(column)
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
context "Draft class extends" do
|
|
90
|
-
|
|
91
|
-
it "ActiveRecord::Base when no options are passed" do
|
|
92
|
-
Article::Draft.superclass.should == ActiveRecord::Base
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
it "the class passed in with the :extends option" do
|
|
96
|
-
BlogPost::Draft.superclass.should == Post
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
context "Draft class belongs to" do
|
|
101
|
-
|
|
102
|
-
it "self when :belongs_to is not specified" do
|
|
103
|
-
Article::Draft.reflect_on_association(:article).macro.should == :belongs_to
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
it "the model passed in with the :belongs_to option" do
|
|
107
|
-
BlogPost::Draft.reflect_on_association(:post).macro.should == :belongs_to
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
end
|
data/spec/spec_helper.rb
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
require "rubygems"
|
|
2
|
-
require "rspec"
|
|
3
|
-
require "factory_girl"
|
|
4
|
-
require "faker"
|
|
5
|
-
require "active_record"
|
|
6
|
-
require "active_support"
|
|
7
|
-
|
|
8
|
-
# Establish DB Connection
|
|
9
|
-
config = YAML::load(IO.read(File.join(File.dirname(__FILE__), 'db', 'database.yml')))
|
|
10
|
-
ActiveRecord::Base.configurations = {'test' => config[ENV['DB'] || 'sqlite3']}
|
|
11
|
-
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
|
|
12
|
-
|
|
13
|
-
# Load Test Schema into the Database
|
|
14
|
-
load(File.dirname(__FILE__) + "/db/schema.rb")
|
|
15
|
-
|
|
16
|
-
require File.dirname(__FILE__) + '/../init'
|
|
17
|
-
|
|
18
|
-
# Example has_draft Model:
|
|
19
|
-
class Article < ActiveRecord::Base
|
|
20
|
-
has_draft
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# Empty class to test ability of Draft class to extend other classes
|
|
24
|
-
class Post < ActiveRecord::Base
|
|
25
|
-
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Model for testing ability to have the draft extend a different class
|
|
29
|
-
class BlogPost < Post
|
|
30
|
-
has_draft :extends => Post, :belongs_to => :post
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# Load Factories:
|
|
34
|
-
Dir[File.join(File.dirname(__FILE__), "factories/**/*.rb")].each {|f| require f}
|