draft_punk 0.3.0 → 0.3.1

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 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