requires_approval 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1,5 @@
1
+ --color
2
+ --format nested
3
+ --fail-fast
4
+ -p
5
+ --order random
data/Gemfile ADDED
@@ -0,0 +1,23 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ gem "activerecord", "~> 3.0.9"
7
+ gem "activesupport", "~> 3.0.9"
8
+
9
+ # Add dependencies to develop your gem here.
10
+ # Include everything needed to run rake, tests, features, etc.
11
+ group :development do
12
+ gem "bundler"
13
+ gem "guard-rspec"
14
+ gem "guard-bundler"
15
+ gem "guard-spork"
16
+ gem "jeweler", "~> 1.8.3"
17
+ gem "mocha"
18
+ gem "rdoc"
19
+ gem "rspec"
20
+ gem "ruby-debug19", :require => "ruby-debug"
21
+ gem "sqlite3"
22
+ gem "yard"
23
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,96 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activemodel (3.0.16)
5
+ activesupport (= 3.0.16)
6
+ builder (~> 2.1.2)
7
+ i18n (~> 0.5.0)
8
+ activerecord (3.0.16)
9
+ activemodel (= 3.0.16)
10
+ activesupport (= 3.0.16)
11
+ arel (~> 2.0.10)
12
+ tzinfo (~> 0.3.23)
13
+ activesupport (3.0.16)
14
+ archive-tar-minitar (0.5.2)
15
+ arel (2.0.10)
16
+ builder (2.1.2)
17
+ columnize (0.3.6)
18
+ diff-lcs (1.1.3)
19
+ ffi (1.1.0)
20
+ git (1.2.5)
21
+ guard (1.2.3)
22
+ listen (>= 0.4.2)
23
+ thor (>= 0.14.6)
24
+ guard-bundler (1.0.0)
25
+ bundler (~> 1.0)
26
+ guard (~> 1.1)
27
+ guard-rspec (1.2.0)
28
+ guard (>= 1.1)
29
+ guard-spork (1.1.0)
30
+ guard (>= 1.1)
31
+ spork (>= 0.8.4)
32
+ i18n (0.5.0)
33
+ jeweler (1.8.4)
34
+ bundler (~> 1.0)
35
+ git (>= 1.2.5)
36
+ rake
37
+ rdoc
38
+ json (1.7.4)
39
+ linecache19 (0.5.12)
40
+ ruby_core_source (>= 0.1.4)
41
+ listen (0.4.7)
42
+ rb-fchange (~> 0.0.5)
43
+ rb-fsevent (~> 0.9.1)
44
+ rb-inotify (~> 0.8.8)
45
+ metaclass (0.0.1)
46
+ mocha (0.12.1)
47
+ metaclass (~> 0.0.1)
48
+ rake (0.9.2.2)
49
+ rb-fchange (0.0.5)
50
+ ffi
51
+ rb-fsevent (0.9.1)
52
+ rb-inotify (0.8.8)
53
+ ffi (>= 0.5.0)
54
+ rdoc (3.12)
55
+ json (~> 1.4)
56
+ rspec (2.11.0)
57
+ rspec-core (~> 2.11.0)
58
+ rspec-expectations (~> 2.11.0)
59
+ rspec-mocks (~> 2.11.0)
60
+ rspec-core (2.11.1)
61
+ rspec-expectations (2.11.1)
62
+ diff-lcs (~> 1.1.3)
63
+ rspec-mocks (2.11.1)
64
+ ruby-debug-base19 (0.11.25)
65
+ columnize (>= 0.3.1)
66
+ linecache19 (>= 0.5.11)
67
+ ruby_core_source (>= 0.1.4)
68
+ ruby-debug19 (0.11.6)
69
+ columnize (>= 0.3.1)
70
+ linecache19 (>= 0.5.11)
71
+ ruby-debug-base19 (>= 0.11.19)
72
+ ruby_core_source (0.1.5)
73
+ archive-tar-minitar (>= 0.5.2)
74
+ spork (0.9.2)
75
+ sqlite3 (1.3.6)
76
+ thor (0.15.4)
77
+ tzinfo (0.3.33)
78
+ yard (0.8.2.1)
79
+
80
+ PLATFORMS
81
+ ruby
82
+
83
+ DEPENDENCIES
84
+ activerecord (~> 3.0.9)
85
+ activesupport (~> 3.0.9)
86
+ bundler
87
+ guard-bundler
88
+ guard-rspec
89
+ guard-spork
90
+ jeweler (~> 1.8.3)
91
+ mocha
92
+ rdoc
93
+ rspec
94
+ ruby-debug19
95
+ sqlite3
96
+ yard
data/Guardfile ADDED
@@ -0,0 +1,42 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'bundler' do
5
+ watch('Gemfile')
6
+ # Uncomment next line if Gemfile contain `gemspec' command
7
+ # watch(/^.+\.gemspec/)
8
+ end
9
+
10
+ guard 'spork', :cucumber_env => { 'RAILS_ENV' => 'test' }, :rspec_env => { 'RAILS_ENV' => 'test' } do
11
+ watch('config/application.rb')
12
+ watch('config/environment.rb')
13
+ watch('config/environments/test.rb')
14
+ watch(%r{^config/initializers/.+\.rb$})
15
+ watch('Gemfile')
16
+ watch('Gemfile.lock')
17
+ watch('spec/spec_helper.rb') { :rspec }
18
+ watch('test/test_helper.rb') { :test_unit }
19
+ watch(%r{features/support/}) { :cucumber }
20
+ end
21
+
22
+ guard 'rspec', :version => 2 do
23
+ watch(%r{^spec/.+_spec\.rb$})
24
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
25
+ watch('spec/spec_helper.rb') { "spec" }
26
+
27
+ # Rails example
28
+ watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
29
+ watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
30
+ watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
31
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
32
+ watch('config/routes.rb') { "spec/routing" }
33
+ watch('app/controllers/application_controller.rb') { "spec/controllers" }
34
+
35
+ # Capybara request specs
36
+ watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
37
+
38
+ # Turnip features and steps
39
+ watch(%r{^spec/acceptance/(.+)\.feature$})
40
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
41
+ end
42
+
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Dan Langevin
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,121 @@
1
+ = requires_approval
2
+
3
+ ===Installation
4
+
5
+ To install, just add requires_approval to your Gemfile
6
+
7
+ # Gemfile
8
+ gem "requires_approval"
9
+
10
+ === Activation
11
+
12
+ To activate, use the requires_approval_for method in your class definition
13
+
14
+ # app/models/user.rb
15
+ #
16
+ # - first_name :string
17
+ # - last_name :string
18
+ # - birthday :date
19
+ # - created_at :datetime
20
+ # - updated_at :datetime
21
+
22
+ class User < ActiveRecord::Base
23
+ requires_approval_for(:first_name, :last_name)
24
+ end
25
+
26
+ And create a migration that adds the necessary table and fields for your
27
+ requires_approval model
28
+
29
+ # db/migrate/TIMESTAMP_add_user_versions.rb
30
+ def self.up
31
+ User.prepare_tables_for_requires_approval
32
+ end
33
+
34
+ This does the equivalent of the the following
35
+
36
+ # Generated migration
37
+ def self.up
38
+ create_table(:user_versions, :force => true) do |t|
39
+ t.string(:first_name)
40
+ t.string(:last_name)
41
+ t.integer(:user_id)
42
+ t.boolean(:is_approved)
43
+ t.timestamps
44
+ end
45
+ add_index(:user_versions, [:user_id, :is_approved])
46
+
47
+ # starts all new records out as frozen so they don't show up
48
+ add_column(:users, :is_frozen, :boolean, :default => true)
49
+
50
+ end
51
+
52
+ === Usage
53
+
54
+ The first_name and last_name methods are now delegated to the user's
55
+ latest_unapproved_version
56
+
57
+ u = User.new
58
+ u.first_name = "Dan"
59
+ u.first_name => nil
60
+ u.latest_unapproved_version.first_name => "Dan"
61
+ u.pending_changes => {
62
+ "first_name" => {
63
+ "was" => nil,
64
+ "became" => "Dan"
65
+ }
66
+ }
67
+ u.save
68
+
69
+ u.approve_attributes(:first_name)
70
+
71
+ u.first_name => "Dan"
72
+
73
+ # it created one version and approved it
74
+ u.versions.count => 1
75
+
76
+ # so it won't show up as an unapproved version
77
+ u.latest_unapproved_version => nil
78
+ u.pending_changes => {}
79
+
80
+ ==== Denying attributes
81
+
82
+ You can also deny changes. If you do so, they just disappear from
83
+ the latest_unapproved_version
84
+
85
+ u = User.create(:first_name => "X", :last_name => "Y")
86
+ u.approve_all_attributes
87
+
88
+ u.update_attribute(:first_name => "Dan")
89
+ u.pending_changes => {
90
+ "first_name" => {
91
+ "was" => "X",
92
+ "became" => "Dan"
93
+ }
94
+ }
95
+
96
+ u.deny_attributes(:first_name)
97
+ # no more changes
98
+ u.pending_changes => {}
99
+
100
+ FYI, this doesn't actually remove the latest_unapproved_version, it just
101
+ sets its values to be the same as the values in the parent record.
102
+
103
+
104
+
105
+
106
+
107
+ == Contributing to requires_approval
108
+
109
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
110
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
111
+ * Fork the project.
112
+ * Start a feature/bugfix branch.
113
+ * Commit and push until you are happy with your contribution.
114
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
115
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
116
+
117
+ == Copyright
118
+
119
+ Copyright (c) 2012 Dan Langevin. See LICENSE.txt for
120
+ further details.
121
+
data/Rakefile ADDED
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "requires_approval"
18
+ gem.homepage = "http://github.com/LifebookerInc/requires_approval"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Gem to handle versioning and things that require approval}
21
+ gem.description = %Q{Gem to handle versioning and things that require approval}
22
+ gem.email = "dan.langevin@lifebooker.com"
23
+ gem.authors = ["Dan Langevin"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'yard'
42
+ YARD::Rake::YardocTask.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
data/lib/errors.rb ADDED
@@ -0,0 +1,11 @@
1
+ module RequiresApproval
2
+
3
+ class CustomError < StandardError; end;
4
+
5
+ class InvalidFieldsError < CustomError; end;
6
+
7
+ class DenyingNeverApprovedError < CustomError; end;
8
+
9
+ class PartialApprovalForNewObject < CustomError; end;
10
+
11
+ end
@@ -0,0 +1,331 @@
1
+ require 'active_record'
2
+ require 'errors'
3
+
4
+
5
+ module RequiresApproval
6
+
7
+ extend ActiveSupport::Concern
8
+
9
+ included do |klass|
10
+ klass.send(:extend, ClassMethods)
11
+ end
12
+
13
+ def approve_all_attributes
14
+ self.approve_attributes(self.fields_requiring_approval)
15
+ end
16
+
17
+ # # approve a list of attributes
18
+ def approve_attributes(*attributes)
19
+
20
+ # validate an normalize our attributes
21
+ attributes = self.check_attributes_for_approval(attributes)
22
+
23
+ # make sure that all attributes are provided if we have never
24
+ # been approved
25
+ fields_not_being_approved = (self.fields_requiring_approval - attributes)
26
+
27
+ if fields_not_being_approved.present? && self.never_approved?
28
+ raise PartialApprovalForNewObject.new(
29
+ "You must approve #{self.fields_requiring_approval.join(", ")} " +
30
+ "for a new #{self.class.name}"
31
+ )
32
+ end
33
+
34
+ attributes.flatten.each do |attr|
35
+ write_attribute(attr, self.latest_unapproved_version.send(attr))
36
+ end
37
+
38
+ # if we have approved all requested changes, make our latest
39
+ # unapproved version approved
40
+ unless self.has_pending_changes?
41
+ self.latest_unapproved_version.update_attribute(:is_approved, true)
42
+ else
43
+ # makes our latest_unapproved_version approved and
44
+ # creates another unapproved version with any remaining
45
+ # attributes
46
+ self.create_approval_version_record
47
+ end
48
+
49
+ self.is_frozen = false
50
+
51
+ self.save
52
+ self.reload
53
+ true
54
+ end
55
+
56
+ def deny_attributes(*attributes)
57
+
58
+ unless self.has_approved_version?
59
+ raise DenyingNeverApprovedError.new
60
+ end
61
+
62
+ attributes = self.check_attributes_for_approval(attributes)
63
+
64
+ attributes.flatten.each do |attr|
65
+ self.latest_unapproved_version.send("#{attr}=", self.send(attr))
66
+ true
67
+ end
68
+
69
+ # if we have denied all changes, remove the record
70
+ unless self.has_pending_changes?
71
+ self.latest_unapproved_version.destroy
72
+ else
73
+ self.latest_unapproved_version.save
74
+ end
75
+
76
+ self.reload
77
+ true
78
+ end
79
+
80
+ # have any of our versions ever been approved?
81
+ def has_approved_version?
82
+ self.versions.where(:is_approved => true).count > 0
83
+ end
84
+
85
+ # have we already approved all outstanding changes?
86
+ def has_pending_changes?
87
+ self.pending_changes.present?
88
+ end
89
+
90
+ # the changes users have requested since the last approval
91
+ def pending_changes
92
+ return {} if self.latest_unapproved_version.blank?
93
+
94
+ ret = {}
95
+ # check each field requiring approval
96
+ self.fields_requiring_approval.each do |field|
97
+ # if it is the same in the unapproved as in the parent table
98
+ # we skip it
99
+ unless self.send(field) == self.latest_unapproved_version.send(field)
100
+ # otherwise we get the change set
101
+ ret[field] = {
102
+ "was" => self.send(field),
103
+ "became" => self.latest_unapproved_version.send(field)
104
+ }
105
+ end
106
+ end
107
+ ret
108
+ end
109
+
110
+ protected
111
+
112
+ # the attributes that require approval
113
+ def attributes_requiring_approval
114
+ self.attributes.select{|k,v| self.fields_requiring_approval.include?(k)}
115
+ end
116
+
117
+ # check if our attributes are valid for approval
118
+ def check_attributes_for_approval(attributes)
119
+ # normalize attributes
120
+ attributes = Array.wrap(attributes).flatten.collect(&:to_s)
121
+
122
+ # check for invalid attributes
123
+ invalid_fields = (attributes - self.fields_requiring_approval)
124
+ # if we have fields not requiring approval, raise an error
125
+ if invalid_fields.present?
126
+ raise InvalidFieldsError.new(
127
+ "fields_requiring_approval don't include #{invalid_fields.join(",")}"
128
+ )
129
+ end
130
+ attributes
131
+ end
132
+
133
+ # creates the record of an individual approval
134
+ def create_approval_version_record
135
+ outstanding_changes = self.pending_attributes
136
+ # update our old latest_unapproved_version to reflect our changes
137
+ self.latest_unapproved_version.update_attributes(
138
+ self.attributes_requiring_approval.merge(:is_approved => true)
139
+ )
140
+ # reload so this unapproved version is out of our cache and will not
141
+ # get its foreign key unassigned
142
+ self.latest_unapproved_version(true)
143
+
144
+ self.latest_unapproved_version = self.versions_class.new(
145
+ self.attributes_requiring_approval.merge(outstanding_changes)
146
+ )
147
+ end
148
+
149
+ # gets the latest unapproved version or creates a new one
150
+ def latest_unapproved_version_with_nil_check
151
+ self.latest_unapproved_version ||= begin
152
+ self.versions_class.new(self.attributes_requiring_approval)
153
+ end
154
+ end
155
+
156
+ # has this record never been approved?
157
+ def never_approved?
158
+ !self.has_approved_version?
159
+ end
160
+
161
+ # ActiveRecord-style attribute hash for the
162
+ # requested changes
163
+ def pending_attributes
164
+ ret = {}
165
+ self.pending_changes.each_pair do |k, change|
166
+ ret[k] = change["became"]
167
+ end
168
+ ret
169
+ end
170
+
171
+ # the class which our versions are
172
+ def versions_class
173
+ self.class.versions_class
174
+ end
175
+
176
+ module ClassMethods
177
+
178
+ # adds the correct tables and columns for requires_approval
179
+ def prepare_tables_for_requires_approval
180
+ self.reset_column_information
181
+
182
+ # adds is_active to the parent table
183
+ self.add_requires_approval_fields
184
+ self.reset_column_information
185
+
186
+ # adds our versions table
187
+ self.drop_versions_table
188
+ self.create_versions_table
189
+
190
+ end
191
+
192
+ def requires_approval_for(*attrs)
193
+ self.set_options(attrs.extract_options!)
194
+
195
+ # set up our attributes that require approval
196
+ self.class_attribute :fields_requiring_approval
197
+ self.fields_requiring_approval = attrs.collect(&:to_s)
198
+
199
+ # set up delegates
200
+ self.set_up_version_delegates
201
+
202
+ # create a blank version before create to handle if no
203
+ # attributes were ever set
204
+ self.before_validation(
205
+ :latest_unapproved_version_with_nil_check,
206
+ :on => :create
207
+ )
208
+
209
+ # create the versions class
210
+ self.create_versions_class
211
+ self.has_many :versions,
212
+ :class_name => self.versions_class.name,
213
+ :foreign_key => self.versions_foreign_key
214
+
215
+ self.has_one :latest_unapproved_version,
216
+ :autosave => true,
217
+ :class_name => self.versions_class.name,
218
+ :foreign_key => self.versions_foreign_key,
219
+ :conditions => [
220
+ "#{self.versions_table_name}.is_approved = ?", false
221
+ ]
222
+ end
223
+
224
+ def unapproved
225
+ includes(:latest_unapproved_version)
226
+ .where("#{self.versions_table_name}.id IS NOT NULL")
227
+ end
228
+
229
+ # the class which our versions are
230
+ def versions_class
231
+ "#{self.name}::#{self.versions_class_name}".constantize
232
+ end
233
+
234
+ protected
235
+
236
+ def add_requires_approval_fields
237
+ # add is_frozen
238
+ unless self.column_names.include?("is_frozen")
239
+ self.connection.add_column(
240
+ self.table_name, :is_frozen, :boolean, :default => true
241
+ )
242
+ end
243
+ # add is_deleted
244
+ unless self.column_names.include?("is_deleted")
245
+ self.connection.add_column(
246
+ self.table_name, :is_deleted, :boolean, :default => false
247
+ )
248
+ end
249
+ true
250
+ end
251
+
252
+ # create a class
253
+ def create_versions_class
254
+ versions_table_name = self.versions_table_name
255
+
256
+ self.const_set self.versions_class_name, Class.new(ActiveRecord::Base)
257
+
258
+ self.versions_class.class_eval do
259
+ self.table_name = versions_table_name
260
+ end
261
+ end
262
+
263
+ def create_versions_table
264
+ self.connection.create_table(self.versions_table_name) do |t|
265
+ self.columns.each do |column|
266
+ t.send(column.type, column.name, {
267
+ :default => column.default,
268
+ :limit => column.limit,
269
+ :null => column.null,
270
+ :precision => column.precision,
271
+ :scale => column.scale
272
+ })
273
+ end
274
+ t.integer self.versions_foreign_key
275
+ t.boolean :is_approved, :default => false
276
+ end
277
+ self.connection.add_index(
278
+ self.versions_table_name,
279
+ [self.versions_foreign_key, :is_approved]
280
+ )
281
+ end
282
+
283
+ # drop the versions table if it exists
284
+ def drop_versions_table
285
+ if self.connection.tables.include?(self.versions_table_name)
286
+ self.connection.drop_table(self.versions_table_name)
287
+ end
288
+ end
289
+
290
+ def set_options(opts = {})
291
+ @versions_class_name = opts.delete(:versions_class_name)
292
+ @version_foreign_key = opts.delete(:versions_foreign_key)
293
+ @versions_table_name = opts.delete(:versions_table_name)
294
+ end
295
+
296
+ def set_up_version_delegates
297
+ self.fields_requiring_approval.each do |f|
298
+ define_method("#{f}=") do |val|
299
+ self.send("#{f}_will_change!")
300
+ self.latest_unapproved_version_with_nil_check.send("#{f}=", val)
301
+ end
302
+ end
303
+ end
304
+
305
+ def validates_approved_field(*args)
306
+ self.versions_class.class_eval do
307
+ validates(*args)
308
+ end
309
+ end
310
+
311
+ # class name for our versions
312
+ def versions_class_name
313
+ @versions_class_name ||= "Version"
314
+ end
315
+
316
+ # foreign key for our class on the version table
317
+ def versions_foreign_key
318
+ @version_foreign_key ||= "#{self.base_class.name.underscore}_id"
319
+ end
320
+
321
+ # table name for our versions
322
+ def versions_table_name
323
+ @versions_table_name ||= "#{self.base_class.name.underscore}_versions"
324
+ end
325
+
326
+ end
327
+
328
+
329
+ end
330
+
331
+ ActiveRecord::Base.send(:include, RequiresApproval)