has_draft 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,10 @@
1
+ == [2010-10-15] 1.0.1 Minor fix to homepage URL
2
+
3
+ == [2010-10-15] 1.0.0 Major Updates
4
+
5
+ * Finally updated to be a Gem
6
+ * Internal refactoring
7
+ * Converted tests to RSpec
8
+ * Added support for Rails 3.0
9
+
10
+ == 0.1.0 Initial Version
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 [name of plugin creator]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,154 @@
1
+ == Has Draft
2
+
3
+ Allows for multiple "drafts" of a model which can be useful when developing:
4
+ * Draft/Live Version of Pages, for examples
5
+ * A workflow system whereby a live copy may need to be active while a draft copy is
6
+ awaiting approval.
7
+
8
+ This was built to be able to be tacked on to existing models, so the data schema doesn't need to change
9
+ at all for the model this is applied to. Drafts are actually stored in a nearly-identical table
10
+ and there is a has_one relationship to this. This separation allows the base model to really be treated
11
+ just as before without having to apply conditions in queries to make sure you are really getting the
12
+ "live" (non-draft) copy: Page.all will still only return the non-draft pages. This separate table is backed by
13
+ a model created on the fly as a constant on the original model class. For example if a Page has_draft,
14
+ a Page::Draft class will exist as the model for the page_drafts table.
15
+
16
+ == Installation
17
+
18
+ This gem requires ActiveRecord 3.0.
19
+
20
+ In your Gemfile:
21
+
22
+ gem "has_draft"
23
+
24
+ == Basic Example
25
+
26
+ ## First Migration (If Creating base model and drafts at the same time):
27
+ class InitialSchema < ActiveRecord::Migration
28
+
29
+ [:articles, :article_drafts].each do |table_name|
30
+ create_table table_name, :force => true do |t|
31
+ t.references :article if table_name == :article_drafts
32
+
33
+ t.string :title
34
+ t.text :summary
35
+ t.text :body
36
+ t.date :post_date
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+
43
+ ## Model Class
44
+ class Article < ActiveRecord::Base
45
+ has_draft
46
+ end
47
+
48
+ ## Exposed Class Methods & Scopes:
49
+ Article.draft_class
50
+ => Article::Draft
51
+ Article.with_draft.all
52
+ => (Articles that have an associated draft)
53
+ Article.without_draft.all
54
+ => (Articles with no associated draft)
55
+
56
+
57
+
58
+ ## Usage Examples:
59
+
60
+ article = Article.create(
61
+ :title => "My Title",
62
+ :summary => "Information here.",
63
+ :body => "Full body",
64
+ :post_date => Date.today
65
+ )
66
+
67
+ article.has_draft?
68
+ => false
69
+
70
+ article.instantiate_draft!
71
+
72
+ article.has_draft?
73
+ => true
74
+
75
+ article.draft
76
+ => Article::Draft Instance
77
+
78
+ article.draft.update_attributes(
79
+ :title => "New Title"
80
+ )
81
+
82
+ article.replace_with_draft!
83
+
84
+ article.title
85
+ => "New Title"
86
+
87
+ article.destroy_draft!
88
+
89
+ article.has_draft?
90
+ => false
91
+
92
+ == Custom Options
93
+
94
+ ## First Migration (If Creating base model and drafts at the same time):
95
+ class InitialSchema < ActiveRecord::Migration
96
+
97
+ [:articles, :article_copies].each do |table_name|
98
+ create_table table_name, :force => true do |t|
99
+ t.integer :news_article_id if table_name == :article_copies
100
+
101
+ t.string :title
102
+ t.text :summary
103
+ t.text :body
104
+ t.date :post_date
105
+ end
106
+ end
107
+
108
+ end
109
+
110
+ ## Model Class
111
+ class Article < ActiveRecord::Base
112
+ has_draft :class_name => 'Copy', :foreign_key => :news_article_id, :table_name => 'article_copies'
113
+ end
114
+
115
+ == Method Callbacks
116
+
117
+ There are three callbacks you can specify directly as methods:
118
+
119
+ class Article < ActiveRecord::Base
120
+ has_draft
121
+
122
+ def before_instantiate_draft
123
+ # Do Something
124
+ end
125
+
126
+ def before_replace_with_draft
127
+ # Do Something
128
+ end
129
+
130
+ def before_destroy_draft
131
+ # Do Something
132
+ end
133
+ end
134
+
135
+
136
+
137
+ == Extending the Draft Class
138
+
139
+ Because you don't directly define the draft class, you can specify a block of code to be run in its
140
+ context by passing a block to has_draft:
141
+
142
+ class Article < ActiveRecord::Base
143
+ belongs_to :user
144
+
145
+ has_draft do
146
+ belongs_to :last_updated_user
147
+
148
+ def approve!
149
+ self.approved_at = Time.now
150
+ self.save
151
+ end
152
+ end
153
+
154
+ end
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require "rubygems"
2
+ require "rake"
3
+ require "rake/rdoctask"
4
+
5
+ desc "Generate documentation for the plugin."
6
+ Rake::RDocTask.new(:rdoc) do |rdoc|
7
+ rdoc.rdoc_dir = "rdoc"
8
+ rdoc.title = "has_draft"
9
+ rdoc.options << "--line-numbers" << "--inline-source"
10
+ rdoc.rdoc_files.include('README')
11
+ rdoc.rdoc_files.include('lib/**/*.rb')
12
+ end
13
+
14
+ Dir["#{File.dirname(__FILE__)}/lib/tasks/*.rake"].sort.each { |ext| load ext }
data/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ require "has_draft"
2
+
3
+ HasDraft::Railtie.insert
@@ -0,0 +1,19 @@
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
@@ -0,0 +1,3 @@
1
+ module HasDraft
2
+ VERSION = "1.0.1"
3
+ end
data/lib/has_draft.rb ADDED
@@ -0,0 +1,120 @@
1
+ module HasDraft
2
+ def self.included(base)
3
+ base.extend ClassMethods
4
+ end
5
+
6
+ module ClassMethods
7
+ def has_draft(options = {}, &block)
8
+ return if self.included_modules.include?(HasDraft::InstanceMethods)
9
+ include HasDraft::InstanceMethods
10
+
11
+ cattr_accessor :draft_class_name, :draft_foreign_key, :draft_table_name, :draft_columns
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}"
16
+
17
+ # Create Relationship to Draft Copy
18
+ class_eval do
19
+ has_one :draft, :class_name => "#{self.to_s}::#{draft_class_name}",
20
+ :foreign_key => draft_foreign_key,
21
+ :dependent => :destroy
22
+
23
+ named_scope :with_draft, :include => [:draft], :conditions => "#{draft_table_name}.id IS NOT NULL"
24
+ named_scope :without_draft, :include => [:draft], :conditions => "#{draft_table_name}.id IS NULL"
25
+ end
26
+
27
+ # Dynamically Create Model::Draft Class
28
+ const_set(draft_class_name, Class.new(ActiveRecord::Base))
29
+
30
+ draft_class.cattr_accessor :original_class
31
+ draft_class.original_class = self
32
+ draft_class.set_table_name(draft_table_name)
33
+
34
+ # Draft Parent Association
35
+ draft_class.belongs_to self.to_s.demodulize.underscore.to_sym, :class_name => "::#{self.to_s}", :foreign_key => draft_foreign_key
36
+
37
+ # Block extension
38
+ draft_class.class_eval(&block) if block_given?
39
+
40
+ # Finally setup which columns to draft
41
+ self.draft_columns = draft_class.new.attributes.keys - [draft_class.primary_key, draft_foreign_key, 'created_at', 'updated_at', inheritance_column]
42
+ end
43
+
44
+ def draft_class
45
+ const_get(draft_class_name)
46
+ end
47
+ end
48
+
49
+ module InstanceMethods
50
+ def has_draft?
51
+ !self.draft.nil?
52
+ end
53
+
54
+ def save_to_draft(perform_validation = true)
55
+ return false if perform_validation and !self.valid?
56
+
57
+ instantiate_draft! unless has_draft?
58
+ copy_attributes_to_draft
59
+
60
+ self.draft.save(perform_validation)
61
+ self.reload
62
+ end
63
+
64
+ def instantiate_draft
65
+ self.draft = self.build_draft
66
+
67
+ copy_attributes_to_draft
68
+ before_instantiate_draft
69
+
70
+ self.draft
71
+ end
72
+
73
+ def instantiate_draft!
74
+ returning instantiate_draft do |draft|
75
+ draft.save unless self.new_record?
76
+ end
77
+ end
78
+
79
+ def copy_attributes_to_draft
80
+ self.class.draft_columns.each do |attribute|
81
+ self.draft.send("#{attribute}=", send(attribute))
82
+ end
83
+ self
84
+ end
85
+
86
+ def copy_attributes_from_draft
87
+ self.class.draft_columns.each do |attribute|
88
+ self.send("#{attribute}=", self.draft.send(attribute))
89
+ end
90
+ self
91
+ end
92
+
93
+ def before_instantiate_draft
94
+ end
95
+
96
+ def replace_with_draft!
97
+ copy_attributes_from_draft
98
+
99
+ before_replace_with_draft
100
+
101
+ self.save unless self.new_record?
102
+ self
103
+ end
104
+
105
+ def before_replace_with_draft
106
+ end
107
+
108
+ def destroy_draft!
109
+ before_destroy_draft
110
+
111
+ self.draft.destroy if self.draft
112
+ self.draft(true)
113
+ end
114
+
115
+ def before_destroy_draft
116
+ end
117
+ end
118
+ end
119
+
120
+ require "has_draft/railtie"
@@ -0,0 +1,21 @@
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 ADDED
@@ -0,0 +1,14 @@
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
+ end
Binary file
@@ -0,0 +1,20 @@
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
@@ -0,0 +1,79 @@
1
+ require "spec_helper"
2
+
3
+ describe HasDraft do
4
+ context "Model with has_draft" do
5
+ it "should expose #draft_class_name as Draft" do
6
+ Article.draft_class_name.should == "Draft"
7
+ end
8
+
9
+ it "should expose #draft_class as Article::Draft" do
10
+ Article.draft_class.should == Article::Draft
11
+ end
12
+
13
+ it "should default #draft_foreign_key as article_id" do
14
+ Article.draft_foreign_key.should == "article_id"
15
+ end
16
+
17
+ it "should default #draft_table_name as article_drafts" do
18
+ Article.draft_table_name.should == "article_drafts"
19
+ end
20
+
21
+ describe "Draft Model" do
22
+ it "should be defined under the Article namespace" do
23
+ Article.constants.should include('Draft')
24
+ end
25
+
26
+ it "should expose #original_class as Article" do
27
+ Article::Draft.original_class.should == Article
28
+ end
29
+ end
30
+ end
31
+
32
+ context "an article" do
33
+ before { @article = Factory.create(:article) }
34
+
35
+ context "when instantiating a new draft" do
36
+ before { @article.instantiate_draft! }
37
+
38
+ it "should create draft" do
39
+ @article.draft.should_not be_nil
40
+ @article.draft.should_not be_new_record
41
+ end
42
+
43
+ it "should copy draft fields" do
44
+ Article.draft_columns.each do |column|
45
+ @article.send(column).should == @article.draft.send(column)
46
+ end
47
+ end
48
+ end
49
+
50
+ context "when destroying an existing draft" do
51
+ before do
52
+ @article = Factory.create(:article_with_draft)
53
+ @article.destroy_draft!
54
+ end
55
+
56
+ it "should associated draft" do
57
+ @article.draft.should be_nil
58
+ end
59
+ end
60
+
61
+ context "when replacing with draft" do
62
+ before do
63
+ @article = Factory.create(:article_with_draft)
64
+ @article.replace_with_draft!
65
+ end
66
+
67
+ it "should still have draft" do
68
+ @article.draft.should_not be_nil
69
+ end
70
+
71
+ it "should now have the same field values as draft" do
72
+ Article.draft_columns.each do |column|
73
+ @article.send(column).should == @article.draft.send(column)
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ end
@@ -0,0 +1,25 @@
1
+ require "rubygems"
2
+ require "rspec"
3
+ require "factory_girl"
4
+ require "faker"
5
+ require "rails"
6
+ require "active_record"
7
+ require "active_support"
8
+
9
+ # Establish DB Connection
10
+ config = YAML::load(IO.read(File.join(File.dirname(__FILE__), 'db', 'database.yml')))
11
+ ActiveRecord::Base.configurations = {'test' => config[ENV['DB'] || 'sqlite3']}
12
+ ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
13
+
14
+ # Load Test Schema into the Database
15
+ load(File.dirname(__FILE__) + "/db/schema.rb")
16
+
17
+ require File.dirname(__FILE__) + '/../init'
18
+
19
+ # Example has_draft Model:
20
+ class Article < ActiveRecord::Base
21
+ has_draft
22
+ end
23
+
24
+ # Load Factories:
25
+ Dir[File.join(File.dirname(__FILE__), "factories/**/*.rb")].each {|f| require f}
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: has_draft
3
+ version: !ruby/object:Gem::Version
4
+ hash: 21
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 1
10
+ version: 1.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Ben Hughes
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-15 00:00:00 +08:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activesupport
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 7
30
+ segments:
31
+ - 3
32
+ - 0
33
+ - 0
34
+ version: 3.0.0
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: activerecord
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 7
46
+ segments:
47
+ - 3
48
+ - 0
49
+ - 0
50
+ version: 3.0.0
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ description: Allows for your ActiveRecord models to have drafts which are stored in a separate duplicate table.
54
+ email: ben@railsgarden.com
55
+ executables: []
56
+
57
+ extensions: []
58
+
59
+ extra_rdoc_files: []
60
+
61
+ files:
62
+ - lib/has_draft/railtie.rb
63
+ - lib/has_draft/version.rb
64
+ - lib/has_draft.rb
65
+ - spec/db/database.yml
66
+ - spec/db/schema.rb
67
+ - spec/db/test.sqlite3
68
+ - spec/factories/article.rb
69
+ - spec/has_draft_spec.rb
70
+ - spec/spec_helper.rb
71
+ - CHANGELOG.rdoc
72
+ - LICENSE
73
+ - Rakefile
74
+ - README.rdoc
75
+ - init.rb
76
+ has_rdoc: true
77
+ homepage: http://github.com/rubiety/has_draft
78
+ licenses: []
79
+
80
+ post_install_message:
81
+ rdoc_options: []
82
+
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ hash: 3
91
+ segments:
92
+ - 0
93
+ version: "0"
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: 19
100
+ segments:
101
+ - 1
102
+ - 3
103
+ - 4
104
+ version: 1.3.4
105
+ requirements: []
106
+
107
+ rubyforge_project: has_draft
108
+ rubygems_version: 1.3.7
109
+ signing_key:
110
+ specification_version: 3
111
+ summary: Attached draft model to your ActiveRecord models.
112
+ test_files: []
113
+