draft_punk 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 904244863752dbaa1f78496c19b136af59d1dba5
4
- data.tar.gz: 8c4e60640cd7728e6988253af6cdbf0bfc5d86d4
3
+ metadata.gz: d72097293299128db20d9aefa7a69a4722eb1a10
4
+ data.tar.gz: abd611675c762f6ac56e51b908489fb11260cc26
5
5
  SHA512:
6
- metadata.gz: 02d77960c5c1771d34b2d2b17bb32d1061e9d53ae8223c64b8df0286814bb3056d24e50a93ef37c51b439be10fed8b79090b6c491d0a822deae18783e618ae8f
7
- data.tar.gz: d7844a4ccdc4e2cfe552499e913d25a805d9bbadfcbdc14974a703fa0d72d13ee34aff01ef93538ea2308f50610eeb0a55b6f424b005ba2d13363a57886c3cd5
6
+ metadata.gz: 5b42ee587d685ad6a454e8aea9b42b014fad40b9aeecfa0b9fa92500c1639bf1bde49a73ebe03bf27bf506fa60bd09d0116faa3ee00f29e986794a5a5ff191bb
7
+ data.tar.gz: 303f073323eb193f5e5a87aed1546abe3251ca09e7456123ac6c98efdc847c30466a5abb180c8228c463743197ee57de1b5fbbd35ffb197b4f9ad24e2f0dd33a
@@ -0,0 +1,52 @@
1
+ module DraftPunk
2
+ module Model
3
+ module ActiveRecordClassMethods
4
+ # List of association names this model is configured to have drafts for
5
+ # @return [Array]
6
+ def draft_target_associations
7
+ targets = if const_defined?(:CREATES_NESTED_DRAFTS_FOR) && const_get(:CREATES_NESTED_DRAFTS_FOR).is_a?(Array)
8
+ const_get(:CREATES_NESTED_DRAFTS_FOR).compact
9
+ else
10
+ default_draft_target_associations
11
+ end.map(&:to_sym)
12
+ end
13
+
14
+ # Whether this model is configured to track the approved version of a draft object.
15
+ # This will be true if the model has an approved_version_id column
16
+ #
17
+ # @return (Boolean)
18
+ def tracks_approved_version?
19
+ column_names.include? 'approved_version_id'
20
+ end
21
+
22
+ # Whether this model is configured to store previously-approved versions of the model.
23
+ # This will be true if the model has an current_approved_version_id column
24
+ #
25
+ # @return (Boolean)
26
+ def tracks_approved_version_history?
27
+ column_names.include?('current_approved_version_id')
28
+ end
29
+
30
+ # Callback runs after save to set the approved version id on the draft object.
31
+ #
32
+ # @return (true)
33
+ def set_approved_version_id_callback
34
+ lambda do |live_obj, draft_obj|
35
+ draft_obj.approved_version_id = live_obj.id if draft_obj.respond_to?(:approved_version_id)
36
+ draft_obj.temporary_approved_object = live_obj
37
+ true
38
+ end
39
+ end
40
+
41
+ protected #################################################################
42
+
43
+ def default_draft_target_associations
44
+ reflect_on_all_associations.select do |reflection|
45
+ DraftPunk.is_relevant_association_type?(reflection) &&
46
+ !reflection.name.in?(%i(draft approved_version)) &&
47
+ reflection.class_name != name # Self referential associations!!! Don't do them!
48
+ end.map{|r| r.name.downcase.to_sym }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -59,13 +59,18 @@ module DraftPunk
59
59
  @live_version = get_approved_version
60
60
  @draft_version = editable_version
61
61
  return unless changes_require_approval? && @draft_version.is_draft? # No-op. ie. the live version is in a state that doesn't require approval.
62
+ @live_version.instance_variable_set :@publishing_draft, true
62
63
  transaction do
63
- create_historic_version_of_approved_object if tracks_approved_version_history?
64
- save_attribute_changes_and_belongs_to_assocations_from_draft
65
- update_has_many_and_has_one_associations_from_draft
66
- # We have to destroy the draft this since we moved all the draft's has_many associations to @live_version. If you call "editable_version" later, it'll build the draft.
67
- # We destroy_all in case extra drafts are in the database. Extra drafts can potentially be created due to race conditions in the application.
68
- self.class.unscoped.where(approved_version_id: @live_version.id).destroy_all
64
+ begin
65
+ create_historic_version_of_approved_object if tracks_approved_version_history?
66
+ save_attribute_changes_and_belongs_to_assocations_from_draft
67
+ update_has_many_and_has_one_associations_from_draft
68
+ # We have to destroy the draft this since we moved all the draft's has_many associations to @live_version. If you call "editable_version" later, it'll build the draft.
69
+ # We destroy_all in case extra drafts are in the database. Extra drafts can potentially be created due to race conditions in the application.
70
+ self.class.unscoped.where(approved_version_id: @live_version.id).destroy_all
71
+ ensure
72
+ @live_version.remove_instance_variable :@publishing_draft
73
+ end
69
74
  end
70
75
  @live_version = self.class.find(@live_version.id)
71
76
  end
@@ -90,6 +95,10 @@ module DraftPunk
90
95
  self.class.tracks_approved_version_history?
91
96
  end
92
97
 
98
+ def publishing_draft?
99
+ !!@publishing_draft
100
+ end
101
+
93
102
  protected #################################################################
94
103
 
95
104
  def get_draft
@@ -190,7 +199,7 @@ module DraftPunk
190
199
  # @return [Boolean] whether the current ActiveRecord object has a draft version
191
200
  def has_draft?
192
201
  raise DraftPunk::ApprovedVersionIdError unless respond_to?(:approved_version_id)
193
- draft.present?
202
+ draft.present? && !publishing_draft?
194
203
  end
195
204
 
196
205
  # @return [Boolean] whether the current ActiveRecord object is a previously-approved
@@ -0,0 +1,70 @@
1
+ require_relative 'setup_model'
2
+
3
+ module DraftPunk
4
+ module Model
5
+ module Macros
6
+ # Call this method in your model to setup approval. It will recursively apply to its associations,
7
+ # thus does not need to be explicity called on its associated models (and will error if you try).
8
+ #
9
+ # This model must have an approved_version_id column (Integer), which will be used to track its draft
10
+ #
11
+ # For instance, your Business model:
12
+ # class Business << ActiveRecord::Base
13
+ # has_many :employees
14
+ # has_many :images
15
+ # has_one :address
16
+ # has_many :vending_machines
17
+ #
18
+ # requires_approval # When creating a business's draft, :employees, :vending_machines, :images, and :address will all have drafts created
19
+ # end
20
+ #
21
+ # Optionally, specify which associations which the user will edit - associations which should have a draft created
22
+ # by defining a CREATES_NESTED_DRAFTS_FOR constant for this model.
23
+ #
24
+ # CREATES_NESTED_DRAFTS_FOR = [:address] # When creating a business's draft, only :address will have drafts created
25
+ #
26
+ # To disable drafts for all assocations for this model, simply pass an empty array:
27
+ # by defining a CREATES_NESTED_DRAFTS_FOR constant for this model.
28
+ # CREATES_NESTED_DRAFTS_FOR = [] # When creating a business's draft, no associations will have drafts created
29
+ #
30
+ # WARNING: If you are setting associations via accepts_nested_attributes all changes to the draft, including associations, get set on the
31
+ # draft object (as expected). If your form includes associated objects which weren't defined in requires_approval, your save will fail since
32
+ # the draft object doesn't HAVE those associations to update! In this case, you should probably add that association to the
33
+ # +associations+ param here.
34
+ #
35
+ # If you want your draft associations to track their live version, add an :approved_version_id column
36
+ # to each association's table. You'll be able to access that associated object's live version, just
37
+ # like you can with the original model which called requires_approval.
38
+ #
39
+ # @param nullify [Array] A list of attributes on this model to set to null on the draft when it is created. For
40
+ # instance, the _id_ and _created_at_ columns are nullified by default, since you don't want Rails to try to
41
+ # persist those on the draft.
42
+ # @param set_default_scope [Boolean] If true, set a default scope on this model for the approved scope; only approved objects
43
+ # will be returned in ActiveRecord queries, unless you call Model.unscoped
44
+ # @param allow_previous_versions_to_be_changed [Boolean] If the model tracks approved version history, and this
45
+ # param is false, previously-approved versions of the object cannot be saved (via a before_save callback )
46
+ # @param associations [Array] Use internally; set associations to create drafts for in the CREATES_NESTED_DRAFTS_FOR constant
47
+ # @return true
48
+ def requires_approval(associations: [], nullify: [], set_default_scope: false, allow_previous_versions_to_be_changed: true)
49
+ DraftPunk::SetupModel.new(self,
50
+ associations: associations,
51
+ nullify: nullify,
52
+ set_default_scope: set_default_scope,
53
+ allow_previous_versions_to_be_changed: allow_previous_versions_to_be_changed
54
+ ).make_approvable!
55
+ end
56
+
57
+ # This will generally be only used in testing scenarios, in cases when requires_approval need to be
58
+ # called multiple times. Only the usage for that use case is supported. Use at your own risk for other
59
+ # use cases.
60
+ def disable_approval!
61
+ send(:remove_const, :DRAFT_PUNK_IS_SETUP) if const_defined? :DRAFT_PUNK_IS_SETUP
62
+ send(:remove_const, :DRAFT_NULLIFY_ATTRIBUTES) if const_defined? :DRAFT_NULLIFY_ATTRIBUTES
63
+ send(:remove_const, :ALLOW_PREVIOUS_VERSIONS_TO_BE_CHANGED) if const_defined? :ALLOW_PREVIOUS_VERSIONS_TO_BE_CHANGED
64
+ fresh_amoeba do
65
+ disable
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,116 @@
1
+ module DraftPunk
2
+
3
+ def self.is_relevant_association_type?(activerecord_reflection)
4
+ activerecord_reflection.macro.in? Amoeba::Config::DEFAULTS[:known_macros]
5
+ end
6
+
7
+ class SetupModel
8
+ require_relative 'activerecord_class_methods'
9
+ require_relative 'activerecord_instance_methods'
10
+ require_relative 'draft_diff_instance_methods'
11
+ require_relative 'previous_version_instance_methods'
12
+
13
+ def initialize(top_level_model, associations: [], nullify: [], set_default_scope: false, allow_previous_versions_to_be_changed: true)
14
+ @top_level_model, @associations, @nullify = top_level_model, associations, nullify
15
+ @set_default_scope, @allow_previous_versions_to_be_changed = set_default_scope, allow_previous_versions_to_be_changed
16
+ end
17
+
18
+ def make_approvable!
19
+ return unless model_table_exists? # Short circuits if you're migrating
20
+ @top_level_model.extend Model::ActiveRecordClassMethods
21
+
22
+ @associations = @top_level_model.draft_target_associations if @associations.empty?
23
+ set_valid_associations @top_level_model, @associations
24
+
25
+ raise DraftPunk::ConfigurationError, "Cannot call requires_approval multiple times for #{@top_level_model.name}" if @top_level_model.const_defined?(:DRAFT_PUNK_IS_SETUP)
26
+ @top_level_model.const_set :DRAFT_NULLIFY_ATTRIBUTES, Array(@nullify).flatten.freeze
27
+ @top_level_model.amoeba do
28
+ nullify @nullify
29
+ # Note that the amoeba associations and customize options are being set in setup_associations_and_scopes_for
30
+ end
31
+ setup_amoeba_for @top_level_model, set_default_scope: @set_default_scope, allow_previous_versions_to_be_changed: @allow_previous_versions_to_be_changed
32
+ true
33
+ end
34
+
35
+ private #####################################################################
36
+
37
+ def model_table_exists?(table_name=@top_level_model.table_name)
38
+ if ActiveRecord::Base.connection.respond_to? :data_source_exists?
39
+ ActiveRecord::Base.connection.data_source_exists?(table_name)
40
+ else
41
+ ActiveRecord::Base.connection.table_exists?(table_name)
42
+ end
43
+ end
44
+
45
+ def setup_amoeba_for(target_model, options={})
46
+ return if target_model.const_defined?(:DRAFT_PUNK_IS_SETUP)
47
+ target_model.extend Model::ActiveRecordClassMethods
48
+ target_associations = target_model.draft_target_associations
49
+ target_associations = set_valid_associations(target_model, target_associations)
50
+ target_model.amoeba do
51
+ enable
52
+ include_associations target_model.const_get(:DRAFT_VALID_ASSOCIATIONS) unless target_model.const_get(:DRAFT_VALID_ASSOCIATIONS).empty?
53
+ customize target_model.set_approved_version_id_callback
54
+ end
55
+ target_model.const_set :DRAFT_PUNK_IS_SETUP, true.freeze
56
+
57
+ setup_associations_and_scopes_for target_model, **options
58
+ setup_draft_association_persistance_for_children_of target_model, target_associations
59
+ end
60
+
61
+ def setup_associations_and_scopes_for(target_model, set_default_scope: false, allow_previous_versions_to_be_changed: true)
62
+ target_model.send :include, Model::InstanceInterrogators unless target_model.method_defined?(:has_draft?)
63
+ target_model.send :attr_accessor, :temporary_approved_object
64
+ target_model.send :before_create, :before_create_draft if target_model.method_defined?(:before_create_draft)
65
+
66
+ target_model.const_set :ALLOW_PREVIOUS_VERSIONS_TO_BE_CHANGED, allow_previous_versions_to_be_changed.freeze
67
+ if target_model.tracks_approved_version_history?
68
+ target_model.belongs_to :current_approved_version, class_name: target_model.name, optional: true
69
+ target_model.has_many :previous_versions, -> { order(id: :desc) }, class_name: target_model.name, foreign_key: :current_approved_version_id
70
+ target_model.before_update :prevent_previous_versions_from_saving
71
+ target_model.send :include, Model::PreviousVersionInstanceMethods
72
+ end
73
+
74
+ if set_default_scope
75
+ target_model.default_scope -> { approved }
76
+ end
77
+
78
+ return if target_model.reflect_on_association(:approved_version) || !target_model.column_names.include?('approved_version_id')
79
+ target_model.send :include, Model::ActiveRecordInstanceMethods
80
+ target_model.send :include, Model::DraftDiffInstanceMethods
81
+ target_model.belongs_to :approved_version, class_name: target_model.name, optional: true
82
+ target_model.scope :approved, -> { where("#{target_model.quoted_table_name}.approved_version_id IS NULL") }
83
+ target_model.has_one :draft, -> { unscope(where: :approved) }, class_name: target_model.name, foreign_key: :approved_version_id
84
+ target_model.scope :draft, -> { unscoped.where("#{target_model.quoted_table_name}.approved_version_id IS NOT NULL") }
85
+ end
86
+
87
+ def setup_draft_association_persistance_for_children_of(target_model, target_associations=nil)
88
+ target_associations = target_model.draft_target_associations unless target_associations
89
+ target_reflections = target_associations.map do |assoc|
90
+ reflection = target_model.reflect_on_association(assoc.to_sym)
91
+ reflection.presence || (raise DraftPunk::ConfigurationError, "#{name} includes invalid association (#{assoc})")
92
+ end
93
+ target_reflections.select{|r| DraftPunk.is_relevant_association_type?(r) }.each do |assoc|
94
+ setup_amoeba_for assoc.klass
95
+ end
96
+ end
97
+
98
+ # Rejects the associations if the table hasn't been defined yet. This happens when
99
+ # running migrations which add that association's table.
100
+ def set_valid_associations(target_model, associations)
101
+ return target_model.const_get(:DRAFT_VALID_ASSOCIATIONS) if target_model.const_defined?(:DRAFT_VALID_ASSOCIATIONS)
102
+ associations = associations.map(&:to_sym)
103
+ valid_assocations = associations.select do |assoc|
104
+ reflection = target_model.reflect_on_association(assoc)
105
+ if reflection
106
+ table_name = reflection.klass.table_name
107
+ model_table_exists?(table_name)
108
+ else
109
+ false
110
+ end
111
+ end
112
+ target_model.const_set :DRAFT_VALID_ASSOCIATIONS, valid_assocations.freeze
113
+ valid_assocations
114
+ end
115
+ end
116
+ end
@@ -1,15 +1,9 @@
1
1
  require 'draft_punk/version'
2
2
  require 'amoeba'
3
- require 'activerecord_class_methods'
4
- require 'helper_methods'
3
+ require 'activerecord/macros'
4
+ require 'helpers/helper_methods'
5
5
 
6
6
  module DraftPunk
7
- module Model
8
- def self.included(base)
9
- base.send :extend, ActiveRecordClassMethods
10
- end
11
- end
12
-
13
7
  class ConfigurationError < RuntimeError
14
8
  def initialize(message)
15
9
  @caller = caller[0]
@@ -46,9 +40,8 @@ module DraftPunk
46
40
  "the draft failed to be created: #{@message}"
47
41
  end
48
42
  end
49
-
50
43
  end
51
44
 
52
45
  ActiveSupport.on_load(:active_record) do
53
- include DraftPunk::Model
46
+ extend DraftPunk::Model::Macros
54
47
  end
@@ -1,3 +1,3 @@
1
1
  module DraftPunk
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: draft_punk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Hodges
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-09-28 00:00:00.000000000 Z
11
+ date: 2018-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: amoeba
@@ -185,13 +185,15 @@ files:
185
185
  - draft_punk-0.2.0.gem
186
186
  - draft_punk-0.2.1.gem
187
187
  - draft_punk.gemspec
188
- - lib/activerecord_class_methods.rb
189
- - lib/activerecord_instance_methods.rb
190
- - lib/draft_diff_instance_methods.rb
188
+ - lib/activerecord/activerecord_class_methods.rb
189
+ - lib/activerecord/activerecord_instance_methods.rb
190
+ - lib/activerecord/draft_diff_instance_methods.rb
191
+ - lib/activerecord/macros.rb
192
+ - lib/activerecord/previous_version_instance_methods.rb
193
+ - lib/activerecord/setup_model.rb
191
194
  - lib/draft_punk.rb
192
195
  - lib/draft_punk/version.rb
193
- - lib/helper_methods.rb
194
- - lib/previous_version_instance_methods.rb
196
+ - lib/helpers/helper_methods.rb
195
197
  homepage: https://github.com/stevehodges/draftpunk
196
198
  licenses:
197
199
  - MIT
@@ -1,212 +0,0 @@
1
- require 'activerecord_instance_methods'
2
- require 'draft_diff_instance_methods'
3
- require 'previous_version_instance_methods'
4
-
5
- module DraftPunk
6
- module Model
7
- module ActiveRecordClassMethods
8
- # Call this method in your model to setup approval. It will recursively apply to its associations,
9
- # thus does not need to be explicity called on its associated models (and will error if you try).
10
- #
11
- # This model must have an approved_version_id column (Integer), which will be used to track its draft
12
- #
13
- # For instance, your Business model:
14
- # class Business << ActiveRecord::Base
15
- # has_many :employees
16
- # has_many :images
17
- # has_one :address
18
- # has_many :vending_machines
19
- #
20
- # requires_approval # When creating a business's draft, :employees, :vending_machines, :images, and :address will all have drafts created
21
- # end
22
- #
23
- # Optionally, specify which associations which the user will edit - associations which should have a draft created
24
- # by defining a CREATES_NESTED_DRAFTS_FOR constant for this model.
25
- #
26
- # CREATES_NESTED_DRAFTS_FOR = [:address] # When creating a business's draft, only :address will have drafts created
27
- #
28
- # To disable drafts for all assocations for this model, simply pass an empty array:
29
- # by defining a CREATES_NESTED_DRAFTS_FOR constant for this model.
30
- # CREATES_NESTED_DRAFTS_FOR = [] # When creating a business's draft, no associations will have drafts created
31
- #
32
- # WARNING: If you are setting associations via accepts_nested_attributes all changes to the draft, including associations, get set on the
33
- # draft object (as expected). If your form includes associated objects which weren't defined in requires_approval, your save will fail since
34
- # the draft object doesn't HAVE those associations to update! In this case, you should probably add that association to the
35
- # +associations+ param here.
36
- #
37
- # If you want your draft associations to track their live version, add an :approved_version_id column
38
- # to each association's table. You'll be able to access that associated object's live version, just
39
- # like you can with the original model which called requires_approval.
40
- #
41
- # @param nullify [Array] A list of attributes on this model to set to null on the draft when it is created. For
42
- # instance, the _id_ and _created_at_ columns are nullified by default, since you don't want Rails to try to
43
- # persist those on the draft.
44
- # @param set_default_scope [Boolean] If true, set a default scope on this model for the approved scope; only approved objects
45
- # will be returned in ActiveRecord queries, unless you call Model.unscoped
46
- # @param allow_previous_versions_to_be_changed [Boolean] If the model tracks approved version history, and this
47
- # param is false, previously-approved versions of the object cannot be saved (via a before_save callback )
48
- # @param associations [Array] Use internally; set associations to create drafts for in the CREATES_NESTED_DRAFTS_FOR constant
49
- # @return true
50
- def requires_approval(associations: [], nullify: [], set_default_scope: false, allow_previous_versions_to_be_changed: true)
51
- return unless draft_punk_table_exists?(table_name) # Short circuits if you're migrating
52
-
53
- associations = draft_target_associations if associations.empty?
54
- set_valid_associations(associations)
55
-
56
- raise DraftPunk::ConfigurationError, "Cannot call requires_approval multiple times for #{name}" if const_defined?(:DRAFT_PUNK_IS_SETUP)
57
- self.const_set :DRAFT_NULLIFY_ATTRIBUTES, Array(nullify).flatten.freeze
58
-
59
- amoeba do
60
- nullify nullify
61
- # Note that the amoeba associations and customize options are being set in setup_associations_and_scopes_for
62
- end
63
- setup_amoeba_for self, set_default_scope: set_default_scope, allow_previous_versions_to_be_changed: allow_previous_versions_to_be_changed
64
- true
65
- end
66
-
67
- # This will generally be only used in testing scenarios, in cases when requires_approval need to be
68
- # called multiple times. Only the usage for that use case is supported. Use at your own risk for other
69
- # use cases.
70
- def disable_approval!
71
- send(:remove_const, :DRAFT_PUNK_IS_SETUP) if const_defined? :DRAFT_PUNK_IS_SETUP
72
- send(:remove_const, :DRAFT_NULLIFY_ATTRIBUTES) if const_defined? :DRAFT_NULLIFY_ATTRIBUTES
73
- send(:remove_const, :ALLOW_PREVIOUS_VERSIONS_TO_BE_CHANGED) if const_defined? :ALLOW_PREVIOUS_VERSIONS_TO_BE_CHANGED
74
- fresh_amoeba do
75
- disable
76
- end
77
- end
78
-
79
- # List of association names this model is configured to have drafts for
80
- # @return [Array]
81
- def draft_target_associations
82
- targets = if const_defined?(:CREATES_NESTED_DRAFTS_FOR) && const_get(:CREATES_NESTED_DRAFTS_FOR).is_a?(Array)
83
- const_get(:CREATES_NESTED_DRAFTS_FOR).compact
84
- else
85
- default_draft_target_associations
86
- end.map(&:to_sym)
87
- end
88
-
89
- # Whether this model is configured to track the approved version of a draft object.
90
- # This will be true if the model has an approved_version_id column
91
- #
92
- # @return (Boolean)
93
- def tracks_approved_version?
94
- column_names.include? 'approved_version_id'
95
- end
96
-
97
- # Whether this model is configured to store previously-approved versions of the model.
98
- # This will be true if the model has an current_approved_version_id column
99
- #
100
- # @return (Boolean)
101
- def tracks_approved_version_history?
102
- column_names.include?('current_approved_version_id')
103
- end
104
-
105
- protected #################################################################
106
-
107
- def default_draft_target_associations
108
- reflect_on_all_associations.select do |reflection|
109
- is_relevant_association_type?(reflection) &&
110
- !reflection.name.in?(%i(draft approved_version)) &&
111
- reflection.class_name != name # Self referential associations!!! Don't do them!
112
- end.map{|r| r.name.downcase.to_sym }
113
- end
114
-
115
- # Rejects the associations if the table hasn't been defined yet. This happens when
116
- # running migrations which add that association's table.
117
- def set_valid_associations(associations)
118
- return const_get(:DRAFT_VALID_ASSOCIATIONS) if const_defined?(:DRAFT_VALID_ASSOCIATIONS)
119
- associations = associations.map(&:to_sym)
120
- valid_assocations = associations.select do |assoc|
121
- reflection = reflect_on_association(assoc)
122
- if reflection
123
- table_name = reflection.klass.table_name
124
- draft_punk_table_exists?(table_name)
125
- else
126
- false
127
- end
128
- end
129
- self.const_set :DRAFT_VALID_ASSOCIATIONS, valid_assocations.freeze
130
- valid_assocations
131
- end
132
-
133
- private ###################################################################
134
-
135
- def setup_amoeba_for(target_class, options={})
136
- return if target_class.const_defined?(:DRAFT_PUNK_IS_SETUP)
137
- associations = target_class.draft_target_associations
138
- associations = target_class.set_valid_associations(associations)
139
- target_class.amoeba do
140
- enable
141
- include_associations target_class.const_get(:DRAFT_VALID_ASSOCIATIONS) unless target_class.const_get(:DRAFT_VALID_ASSOCIATIONS).empty?
142
- customize target_class.set_approved_version_id_callback
143
- end
144
- target_class.const_set :DRAFT_PUNK_IS_SETUP, true.freeze
145
-
146
- setup_associations_and_scopes_for target_class, **options
147
- setup_draft_association_persistance_for_children_of target_class, associations
148
- end
149
-
150
- public ###################################################################
151
-
152
- def set_approved_version_id_callback
153
- lambda do |live_obj, draft_obj|
154
- draft_obj.approved_version_id = live_obj.id if draft_obj.respond_to?(:approved_version_id)
155
- draft_obj.temporary_approved_object = live_obj
156
- end
157
- end
158
-
159
- private ###################################################################
160
-
161
- def setup_draft_association_persistance_for_children_of(target_class, associations=nil)
162
- associations = target_class.draft_target_associations unless associations
163
- target_reflections = associations.map do |assoc|
164
- reflection = target_class.reflect_on_association(assoc.to_sym)
165
- reflection.presence || (raise DraftPunk::ConfigurationError, "#{name} includes invalid association (#{assoc})")
166
- end
167
- target_reflections.select{|r| is_relevant_association_type?(r) }.each do |assoc|
168
- setup_amoeba_for assoc.klass
169
- end
170
- end
171
-
172
- def setup_associations_and_scopes_for(target_class, set_default_scope: false, allow_previous_versions_to_be_changed: true)
173
- target_class.send :include, InstanceInterrogators unless target_class.method_defined?(:has_draft?)
174
- target_class.send :attr_accessor, :temporary_approved_object
175
- target_class.send :before_create, :before_create_draft if target_class.method_defined?(:before_create_draft)
176
-
177
- target_class.const_set :ALLOW_PREVIOUS_VERSIONS_TO_BE_CHANGED, allow_previous_versions_to_be_changed.freeze
178
- if target_class.tracks_approved_version_history?
179
- target_class.belongs_to :current_approved_version, class_name: target_class.name, optional: true
180
- target_class.has_many :previous_versions, -> { order(id: :desc) }, class_name: target_class.name, foreign_key: :current_approved_version_id
181
- target_class.before_update :prevent_previous_versions_from_saving
182
- target_class.send :include, PreviousVersionInstanceMethods
183
- end
184
-
185
- if set_default_scope
186
- target_class.default_scope -> { approved }
187
- end
188
-
189
- return if target_class.reflect_on_association(:approved_version) || !target_class.column_names.include?('approved_version_id')
190
- target_class.send :include, ActiveRecordInstanceMethods
191
- target_class.send :include, DraftDiffInstanceMethods
192
- target_class.belongs_to :approved_version, class_name: target_class.name, optional: true
193
- target_class.scope :approved, -> { where("#{target_class.quoted_table_name}.approved_version_id IS NULL") }
194
- target_class.has_one :draft, -> { unscope(where: :approved) }, class_name: target_class.name, foreign_key: :approved_version_id
195
- target_class.scope :draft, -> { unscoped.where("#{target_class.quoted_table_name}.approved_version_id IS NOT NULL") }
196
- end
197
-
198
- def is_relevant_association_type?(activerecord_reflection)
199
- activerecord_reflection.macro.in? Amoeba::Config::DEFAULTS[:known_macros]
200
- end
201
-
202
- def draft_punk_table_exists?(table_name)
203
- if ActiveRecord::Base.connection.respond_to? :data_source_exists?
204
- ActiveRecord::Base.connection.data_source_exists?(table_name)
205
- else
206
- ActiveRecord::Base.connection.table_exists?(table_name)
207
- end
208
- end
209
- end
210
-
211
- end
212
- end