rails_admin_draft 0.1.0 → 0.1.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 +4 -4
- data/config/initializers/draft_status.rb +4 -0
- data/config/locales/rails_admin_draft/en.yml +36 -0
- data/config/locales/rails_admin_draft/fr.yml +36 -0
- data/lib/generators/draft_migration_generator.rb +22 -0
- data/lib/rails_admin_draft/concern/draft_concern.rb +126 -0
- data/lib/rails_admin_draft/configuration.rb +19 -0
- data/lib/rails_admin_draft/engine.rb +10 -0
- data/lib/rails_admin_draft/model.rb +12 -0
- data/lib/rails_admin_draft/rails_admin/config/actions/copy_as_draft.rb +42 -0
- data/lib/rails_admin_draft/rails_admin/config/actions/edit.rb +75 -0
- data/lib/rails_admin_draft/rails_admin/config/actions/new.rb +80 -0
- data/lib/rails_admin_draft/services/draft/clone_service.rb +73 -0
- data/lib/rails_admin_draft/services/draft/overwrite_service.rb +62 -0
- data/lib/rails_admin_draft/version.rb +3 -0
- data/lib/rails_admin_draft.rb +16 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0536b226a2009c0cea3563c66e3eb6e200a7b006cc211206a71c311f00e3964
|
4
|
+
data.tar.gz: ac2c6be6868bb2099ee3b8d68a3cb19efa5ce4e6e76850d164a1959cd9c17527
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0c539d9f9642a1e39aff7fae1f90d406b30380f1b0d52392cb4ad9e447fcc2a9bf9693d3386454814340ef98404f843108b096acb05ac7f61acb53e27781c4eb
|
7
|
+
data.tar.gz: 78dd9721239eb16052a5f44ba685d37d9f915c6b923945b7dfa0ac7b379139ed2259644bf5213b6bf9abe2fe3b68119467ef78637a84f4969a2ec946ea4853fc
|
@@ -0,0 +1,36 @@
|
|
1
|
+
en:
|
2
|
+
admin:
|
3
|
+
actions:
|
4
|
+
copy_as_draft:
|
5
|
+
title: Copy as draft "%{object_label}"
|
6
|
+
menu: Copy as draft
|
7
|
+
breadcrumb: Copy as draft
|
8
|
+
success: copied as draft
|
9
|
+
publish:
|
10
|
+
success: published
|
11
|
+
switch_to_draft:
|
12
|
+
success: switched to draft
|
13
|
+
|
14
|
+
scopes:
|
15
|
+
rails_admin_all: All
|
16
|
+
rails_admin_draft: Drafts
|
17
|
+
rails_admin_published: Published
|
18
|
+
|
19
|
+
form:
|
20
|
+
draft_header: Draft
|
21
|
+
buttons:
|
22
|
+
save_draft:
|
23
|
+
title: Save as draft
|
24
|
+
name: Save as draft
|
25
|
+
switch_to_draft:
|
26
|
+
title: Switch to draft
|
27
|
+
name: Switch to draft
|
28
|
+
copy_as_draft:
|
29
|
+
title: Copy as draft
|
30
|
+
name: Copy as draft
|
31
|
+
publish:
|
32
|
+
title: Publish
|
33
|
+
name: Publish
|
34
|
+
|
35
|
+
draft_status:
|
36
|
+
draft: Draft
|
@@ -0,0 +1,36 @@
|
|
1
|
+
fr:
|
2
|
+
admin:
|
3
|
+
actions:
|
4
|
+
copy_as_draft:
|
5
|
+
title: Dupliquer en brouillon "%{object_label}"
|
6
|
+
menu: Dupliquer en brouillon
|
7
|
+
breadcrumb: Dupliquer en brouillon
|
8
|
+
success: dupliqué(e) en brouillon
|
9
|
+
publish:
|
10
|
+
success: publié(e)
|
11
|
+
switch_to_draft:
|
12
|
+
success: basculé(e) en brouillon
|
13
|
+
|
14
|
+
scopes:
|
15
|
+
rails_admin_all: Tous
|
16
|
+
rails_admin_draft: Brouillons
|
17
|
+
rails_admin_published: Publiés
|
18
|
+
|
19
|
+
form:
|
20
|
+
draft_header: Brouillon
|
21
|
+
buttons:
|
22
|
+
save_draft:
|
23
|
+
title: Enregistrer au statut brouillon. Ne sera pas visible sur le site public.
|
24
|
+
name: Enregistrer le brouillon
|
25
|
+
switch_to_draft:
|
26
|
+
title: Basculer au statut brouillon. Ne sera plus visible sur le site public.
|
27
|
+
name: Basculer en brouillon
|
28
|
+
copy_as_draft:
|
29
|
+
title: Faire une copie au statut brouillon. Cette copie remplacera l'originale lorsque publiée. L'originale reste visible sur le site public.
|
30
|
+
name: Dupliquer en brouillon
|
31
|
+
publish:
|
32
|
+
title: Rendre visible sur le site public.
|
33
|
+
name: Publier
|
34
|
+
|
35
|
+
draft_status:
|
36
|
+
draft: Brouillon
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class DraftMigrationGenerator < Rails::Generators::Base
|
2
|
+
class_option :table, type: :string, required: true
|
3
|
+
|
4
|
+
def create_draft_migration_file
|
5
|
+
table = options['table'].downcase
|
6
|
+
id_type = table.capitalize.constantize.columns_hash['id'].type
|
7
|
+
id_unsigned = table.capitalize.constantize.columns_hash['id'].sql_type.include?('unsigned')
|
8
|
+
|
9
|
+
filename = "db/migrate/#{Time.current.strftime("%Y%m%d%H%M%S")}_add_draft_to_#{table}.rb"
|
10
|
+
table_name = table.pluralize
|
11
|
+
create_file(filename, <<-FILE
|
12
|
+
class AddDraftTo#{table_name.capitalize} < ActiveRecord::Migration[7.0]
|
13
|
+
def change
|
14
|
+
add_column :#{table_name}, :draft_status, :string, null: true, default: DraftStatus::DRAFT
|
15
|
+
add_column :#{table_name}, :draft_source_id, :#{id_type}, null: true#{id_unsigned ? ', unsigned: true' : nil }
|
16
|
+
add_foreign_key :#{table_name}, :#{table_name}, column: :draft_source_id, name: :fk_#{table_name}_draft_source_id, on_delete: :cascade, on_update: :cascade
|
17
|
+
end
|
18
|
+
end
|
19
|
+
FILE
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module DraftConcern
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
amoeba do
|
6
|
+
enable
|
7
|
+
end
|
8
|
+
|
9
|
+
# == Attributes ===========================================================
|
10
|
+
enum draft_status: [DraftStatus::DRAFT, DraftStatus::PUBLISHED].index_with { |cs| cs.to_s }
|
11
|
+
|
12
|
+
# == Validations ==========================================================
|
13
|
+
validates_presence_of :draft_status
|
14
|
+
|
15
|
+
# == Scopes ===============================================================
|
16
|
+
scope :draft, -> { where(draft_status: DraftStatus::DRAFT) }
|
17
|
+
scope :published, -> { where(draft_status: DraftStatus::PUBLISHED) }
|
18
|
+
scope :rails_admin_all, -> { includes(:draft) }
|
19
|
+
scope :rails_admin_draft, -> { rails_admin_all.draft }
|
20
|
+
scope :rails_admin_published, -> { rails_admin_all.published }
|
21
|
+
|
22
|
+
# == Instance Methods =====================================================
|
23
|
+
# Adds 'draft' to your model's title if it's a draft. Used to display in rails_admin listing.
|
24
|
+
def title_with_draft_status
|
25
|
+
title = self.send(self.class.title_attribute)
|
26
|
+
return "#{title} - <i style='color: grey;'>#{I18n.t('admin.draft_status.draft')}</i>".html_safe if self.is_draft?
|
27
|
+
return title
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns whether the record is a draft
|
31
|
+
def is_draft?
|
32
|
+
return self.draft_status == DraftStatus::DRAFT
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns whether the record is published
|
36
|
+
def is_published?
|
37
|
+
return self.draft_status == DraftStatus::PUBLISHED
|
38
|
+
end
|
39
|
+
|
40
|
+
# Makes a draft copy of the record if it is published
|
41
|
+
def copy_as_draft
|
42
|
+
return if self.is_draft?
|
43
|
+
|
44
|
+
clone = Draft::CloneService.new(model: self).perform
|
45
|
+
clone.draft_status = DraftStatus::DRAFT
|
46
|
+
clone.draft_source_id = self.id
|
47
|
+
add_draft_on_unique_text_fields(clone)
|
48
|
+
clone.save!
|
49
|
+
|
50
|
+
return clone
|
51
|
+
end
|
52
|
+
|
53
|
+
# Publish the record. Overwrite the source record if present.
|
54
|
+
def publish
|
55
|
+
return if !self.is_draft?
|
56
|
+
model_to_publish = self
|
57
|
+
|
58
|
+
ActiveRecord::Base.transaction do
|
59
|
+
if self.draft_source_id.present?
|
60
|
+
model_to_publish = Draft::OverwriteService.new(source: self, destination: self.draft_source).perform
|
61
|
+
model_to_publish.slug = self.slug # Keep same slug
|
62
|
+
if Object.const_defined?('DiscardConcern') && self.class.include?('DiscardConcern'.constantize)
|
63
|
+
self.destroy(true)
|
64
|
+
else
|
65
|
+
self.destroy
|
66
|
+
end
|
67
|
+
remove_draft_on_unique_text_fields(model_to_publish)
|
68
|
+
end
|
69
|
+
|
70
|
+
model_to_publish.draft_status = DraftStatus::PUBLISHED
|
71
|
+
model_to_publish.save!
|
72
|
+
end
|
73
|
+
|
74
|
+
return model_to_publish
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def add_draft_on_unique_text_fields(model)
|
80
|
+
return if !self.class.unique_text_fields.present?
|
81
|
+
|
82
|
+
self.class.unique_text_fields.each do |field|
|
83
|
+
next if !model[field].present? || !model[field].is_a?(String)
|
84
|
+
model[field] = "#{DraftStatus::DRAFT}-#{model[field]}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def remove_draft_on_unique_text_fields(model)
|
89
|
+
return if !self.class.unique_text_fields.present?
|
90
|
+
|
91
|
+
self.class.unique_text_fields.each do |field|
|
92
|
+
next if !model[field].present? || !model[field].is_a?(String) || !model[field].match(/^#{Regexp.quote(DraftStatus::DRAFT)}-*./,)
|
93
|
+
model[field] = model[field].remove("#{DraftStatus::DRAFT}-")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
class_methods do
|
100
|
+
# Creates bi-directional association for draft source.
|
101
|
+
def set_draft_relation(klass_name:)
|
102
|
+
association_class_name = klass_name.to_s
|
103
|
+
class_eval do
|
104
|
+
has_one :draft, class_name: "#{association_class_name}", foreign_key: :draft_source_id, inverse_of: :draft_source
|
105
|
+
belongs_to :draft_source, class_name: "#{association_class_name}", optional: true
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# List of string fields that are unique. When you copy a published record as draft, the string 'draft-' will be added at the beginning of each field to avoid unique constraint errors. The string 'draft-' is removed when the record is published.
|
110
|
+
def unique_text_fields
|
111
|
+
return [:slug]
|
112
|
+
end
|
113
|
+
|
114
|
+
# When you copy a record as draft, its uploads are also copied. To prevent an upload of being copied, add it to this list. You can also add attributes in associated tables.
|
115
|
+
# Ex: [ :image, { relation_name: :sub_pages, attribute: :image } ]
|
116
|
+
def except_upload_attributes
|
117
|
+
return []
|
118
|
+
end
|
119
|
+
|
120
|
+
# The attribute that represent the title/name of the record. Used by the `title_with_draft_status` method.
|
121
|
+
def title_attribute
|
122
|
+
return :title
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module RailsAdminDraft
|
2
|
+
class Configuration
|
3
|
+
|
4
|
+
def initialize(abstract_model)
|
5
|
+
@abstract_model = abstract_model
|
6
|
+
end
|
7
|
+
|
8
|
+
def draft?
|
9
|
+
draft.present?
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def draft
|
15
|
+
@has_draft ||= ::RailsAdmin::Config.model(@abstract_model.model).has_draft
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'rails'
|
2
|
+
require 'rails_admin_draft'
|
3
|
+
|
4
|
+
module RailsAdminDraft
|
5
|
+
class Engine < ::Rails::Engine
|
6
|
+
initializer "RailsAdminDraft precompile hook", group: :all do |app|
|
7
|
+
app.config.assets.precompile += %w(rails_admin/rails_admin_draft.css)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module RailsAdmin
|
2
|
+
module Config
|
3
|
+
module Actions
|
4
|
+
class CopyAsDraft < Base
|
5
|
+
RailsAdmin::Config::Actions.register(self)
|
6
|
+
|
7
|
+
register_instance_option :member do
|
8
|
+
true
|
9
|
+
end
|
10
|
+
|
11
|
+
register_instance_option :http_methods do
|
12
|
+
[:get]
|
13
|
+
end
|
14
|
+
|
15
|
+
register_instance_option :controller do
|
16
|
+
Proc.new do
|
17
|
+
new_draft = @object.copy_as_draft
|
18
|
+
action_message = t('admin.actions.copy_as_draft.success')
|
19
|
+
flash[:success] = t('admin.flash.successful', name: @model_config.label, action: action_message)
|
20
|
+
redirect_to edit_path(params[:model_name], new_draft.id)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
register_instance_option :template_name do
|
25
|
+
:new
|
26
|
+
end
|
27
|
+
|
28
|
+
register_instance_option :link_icon do
|
29
|
+
'fas fa-file-circle-plus'
|
30
|
+
end
|
31
|
+
|
32
|
+
register_instance_option :pjax? do
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
register_instance_option :visible? do
|
37
|
+
bindings[:object].respond_to?(:is_published?) && bindings[:object].respond_to?(:draft_source_id) && bindings[:object].is_published? && !bindings[:object].draft.present?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsAdmin
|
4
|
+
module Config
|
5
|
+
module Actions
|
6
|
+
class Edit < RailsAdmin::Config::Actions::Base
|
7
|
+
RailsAdmin::Config::Actions.register(self)
|
8
|
+
|
9
|
+
register_instance_option :member do
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
register_instance_option :http_methods do
|
14
|
+
%i[get put]
|
15
|
+
end
|
16
|
+
|
17
|
+
register_instance_option :controller do
|
18
|
+
proc do
|
19
|
+
if request.get? # EDIT
|
20
|
+
@draft_conf = ::RailsAdminDraft::Configuration.new @abstract_model
|
21
|
+
|
22
|
+
respond_to do |format|
|
23
|
+
format.html { render @action.template_name }
|
24
|
+
format.js { render @action.template_name, layout: 'rails_admin/modal', content_type: Mime[:html].to_s }
|
25
|
+
end
|
26
|
+
|
27
|
+
elsif request.put? # UPDATE
|
28
|
+
sanitize_params_for!(request.xhr? ? :modal : :update)
|
29
|
+
|
30
|
+
@object.assign_attributes(params[@abstract_model.param_key])
|
31
|
+
@authorization_adapter&.authorize(:update, @abstract_model, @object)
|
32
|
+
changes = @object.changes
|
33
|
+
|
34
|
+
if params[:_publish]
|
35
|
+
@object = @object.publish
|
36
|
+
custom_action_name = 'publish'
|
37
|
+
elsif params[:_switch_to_draft]
|
38
|
+
@object.draft_status = DraftStatus::DRAFT
|
39
|
+
custom_action_name = 'switch_to_draft'
|
40
|
+
elsif params[:_copy_as_draft]
|
41
|
+
@object = @object.copy_as_draft
|
42
|
+
custom_action_name = 'copy_as_draft'
|
43
|
+
end
|
44
|
+
|
45
|
+
if @object.save
|
46
|
+
@auditing_adapter&.update_object(@object, @abstract_model, _current_user, changes)
|
47
|
+
|
48
|
+
if custom_action_name.present?
|
49
|
+
flash[:success] = t('admin.flash.successful', name: @model_config.label, action: t("admin.actions.#{custom_action_name}.success"))
|
50
|
+
redirect_to edit_path(params[:model_name], @object.id)
|
51
|
+
else
|
52
|
+
respond_to do |format|
|
53
|
+
format.html { redirect_to_on_success }
|
54
|
+
format.json { render json: { id: @object.id.to_s, label: @model_config.with(object: @object).object_label } }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
else
|
58
|
+
handle_save_error :edit
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
register_instance_option :link_icon do
|
66
|
+
'fas fa-pencil-alt'
|
67
|
+
end
|
68
|
+
|
69
|
+
register_instance_option :writable? do
|
70
|
+
!(bindings[:object] && bindings[:object].readonly?)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsAdmin
|
4
|
+
module Config
|
5
|
+
module Actions
|
6
|
+
class New < RailsAdmin::Config::Actions::Base
|
7
|
+
RailsAdmin::Config::Actions.register(self)
|
8
|
+
|
9
|
+
register_instance_option :collection do
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
register_instance_option :http_methods do
|
14
|
+
%i[get post] # NEW / CREATE
|
15
|
+
end
|
16
|
+
|
17
|
+
register_instance_option :controller do
|
18
|
+
proc do
|
19
|
+
if request.get? # NEW
|
20
|
+
|
21
|
+
@object = @abstract_model.new
|
22
|
+
@action = @action.with(@action.bindings.merge(object: @object))
|
23
|
+
@authorization_adapter&.attributes_for(:new, @abstract_model)&.each do |name, value|
|
24
|
+
@object.send("#{name}=", value)
|
25
|
+
end
|
26
|
+
object_params = params[@abstract_model.param_key]
|
27
|
+
if object_params
|
28
|
+
sanitize_params_for!(request.xhr? ? :modal : :create)
|
29
|
+
@object.assign_attributes(@object.attributes.merge(object_params.to_h))
|
30
|
+
end
|
31
|
+
respond_to do |format|
|
32
|
+
format.html { render @action.template_name }
|
33
|
+
format.js { render @action.template_name, layout: 'rails_admin/modal', content_type: Mime[:html].to_s }
|
34
|
+
end
|
35
|
+
|
36
|
+
elsif request.post? # CREATE
|
37
|
+
|
38
|
+
@modified_assoc = []
|
39
|
+
@object = @abstract_model.new
|
40
|
+
sanitize_params_for!(request.xhr? ? :modal : :create)
|
41
|
+
|
42
|
+
@object.assign_attributes(params[@abstract_model.param_key])
|
43
|
+
@authorization_adapter&.authorize(:create, @abstract_model, @object)
|
44
|
+
|
45
|
+
if params[:_publish]
|
46
|
+
@object.publish
|
47
|
+
custom_action_name = 'publish'
|
48
|
+
end
|
49
|
+
|
50
|
+
if @object.save
|
51
|
+
@auditing_adapter&.create_object(@object, @abstract_model, _current_user)
|
52
|
+
|
53
|
+
if custom_action_name.present?
|
54
|
+
flash[:success] = t('admin.flash.successful', name: @model_config.label, action: t("admin.actions.#{custom_action_name}.success"))
|
55
|
+
redirect_to edit_path(params[:model_name], @object.id)
|
56
|
+
else
|
57
|
+
respond_to do |format|
|
58
|
+
format.html { redirect_to_on_success }
|
59
|
+
format.json { render json: { id: @object.id.to_s, label: @model_config.with(object: @object).object_label } }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
else
|
63
|
+
handle_save_error
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
register_instance_option :link_icon do
|
71
|
+
'fas fa-plus'
|
72
|
+
end
|
73
|
+
|
74
|
+
register_instance_option :writable? do
|
75
|
+
!(bindings[:object] && bindings[:object].readonly?)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Draft
|
2
|
+
class CloneService
|
3
|
+
DRAFT_ASSOCIATIONS = [:draft, :draft_source]
|
4
|
+
|
5
|
+
def initialize(model:)
|
6
|
+
@model = model
|
7
|
+
end
|
8
|
+
|
9
|
+
# Performs the clone. Handled by amoeba, except uploads which are handled manually.
|
10
|
+
def perform
|
11
|
+
clone = @model.amoeba_dup
|
12
|
+
clone_uploads(clone)
|
13
|
+
|
14
|
+
return clone
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
# Clone uploads for the model and its associations (has_one & has_many) excluding except_upload_attributes.
|
20
|
+
def clone_uploads(clone)
|
21
|
+
attributes = upload_attributes(clone.class)
|
22
|
+
return if attributes.blank?
|
23
|
+
|
24
|
+
attributes.each do |attribute|
|
25
|
+
next if !@model.send(attribute).present? || attribute_is_excluded?(attribute)
|
26
|
+
clone.send("#{attribute}=", File.open(@model.send(attribute).file.path))
|
27
|
+
end
|
28
|
+
|
29
|
+
clone_associations_uploads(clone, :has_one)
|
30
|
+
clone_associations_uploads(clone, :has_many)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Clone uploads for the model's associations excluding except_upload_attributes.
|
34
|
+
def clone_associations_uploads(clone, association_type)
|
35
|
+
clone.class.reflect_on_all_associations(association_type).each do |association|
|
36
|
+
next if DRAFT_ASSOCIATIONS.include?(association.name) || association.options[:through].present?
|
37
|
+
|
38
|
+
attributes = upload_attributes(association.class_name.constantize)
|
39
|
+
next if attributes.blank?
|
40
|
+
|
41
|
+
attributes.each do |attribute|
|
42
|
+
relation = @model.send(association.name)
|
43
|
+
next if !relation.present? || attribute_is_excluded?(attribute, association.name)
|
44
|
+
|
45
|
+
if relation.is_a?(ActiveRecord::Associations::CollectionProxy)
|
46
|
+
relation.each_with_index do |relation_instance, index|
|
47
|
+
next if !relation_instance.send(attribute).present?
|
48
|
+
clone.send(association.name)[index].send("#{attribute}=", File.open(relation_instance.send(attribute).file.path))
|
49
|
+
end
|
50
|
+
else
|
51
|
+
next if !relation.send(attribute).present?
|
52
|
+
clone.send(association.name).send("#{attribute}=", File.open(relation.send(attribute).file.path))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the upload attributes for a given class.
|
59
|
+
def upload_attributes(klass)
|
60
|
+
return [] if !klass.respond_to?(:uploaders)
|
61
|
+
return klass.uploaders.keys
|
62
|
+
end
|
63
|
+
|
64
|
+
# Checks if a given attribute is excluded from the clone operation.
|
65
|
+
def attribute_is_excluded?(attribute, relation_name = nil)
|
66
|
+
except_upload_attributes = @model.class.except_upload_attributes
|
67
|
+
return false if !except_upload_attributes.present?
|
68
|
+
return except_upload_attributes.include?(attribute) if relation_name.nil?
|
69
|
+
return except_upload_attributes.include?({ relation_name: relation_name, attribute: attribute })
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Draft
|
2
|
+
class OverwriteService
|
3
|
+
DRAFT_ASSOCIATIONS = [:draft, :draft_source]
|
4
|
+
|
5
|
+
def initialize(source:, destination:)
|
6
|
+
raise "Source must be an ActiveRecord::Base" if !source.is_a?(ActiveRecord::Base)
|
7
|
+
raise "Destination must be an ActiveRecord::Base" if !destination.is_a?(ActiveRecord::Base)
|
8
|
+
raise "Source and destination must be of the same type" if source.class != destination.class
|
9
|
+
|
10
|
+
@source = source
|
11
|
+
@destination = destination
|
12
|
+
end
|
13
|
+
|
14
|
+
# Performs the overwrite. Handles attributes, associations and uploads.
|
15
|
+
def perform
|
16
|
+
@destination.assign_attributes(@source.attributes.except("id", "draft_source_id", *upload_attributes.map(&:to_s)))
|
17
|
+
handle_associations(:has_one)
|
18
|
+
handle_associations(:has_many)
|
19
|
+
handle_associations(:has_and_belongs_to_many)
|
20
|
+
handle_uploads
|
21
|
+
|
22
|
+
@destination.save!
|
23
|
+
return @destination.reload
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Overwrites the destination associations with the source's. Handles has_one, has_many and has_and_belongs_to_many associations.
|
29
|
+
def handle_associations(association_type)
|
30
|
+
@source.class.reflect_on_all_associations(association_type).each do |association|
|
31
|
+
next if DRAFT_ASSOCIATIONS.include?(association.name) || association.options[:through].present?
|
32
|
+
|
33
|
+
if association_type == :has_and_belongs_to_many
|
34
|
+
@destination.send("#{association.plural_name}=", @source.send("#{association.plural_name}"))
|
35
|
+
else
|
36
|
+
association_model = association.class_name.constantize
|
37
|
+
association_model.where("#{association.foreign_key} = #{@destination.id}").destroy_all
|
38
|
+
association_model.where("#{association.foreign_key} = #{@source.id}").update_all("#{association.foreign_key} = #{@destination.id}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Overwrites the destination uploads with the source's.
|
44
|
+
def handle_uploads
|
45
|
+
attributes = upload_attributes
|
46
|
+
return if attributes.blank?
|
47
|
+
|
48
|
+
attributes.each do |attribute|
|
49
|
+
@destination.send(attribute).remove! if @destination.send(attribute).present?
|
50
|
+
new_upload = @source.send(attribute).present? ? File.open(@source.send(attribute).file.path) : nil
|
51
|
+
@destination.send("#{attribute}=", new_upload)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Attributes that contain an uploader.
|
56
|
+
def upload_attributes
|
57
|
+
return [] if !@source.class.respond_to?(:uploaders)
|
58
|
+
return @source.class.uploaders.keys
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rails_admin_draft/version'
|
2
|
+
require 'rails_admin/config/model'
|
3
|
+
require 'rails_admin/config/actions'
|
4
|
+
|
5
|
+
require 'rails_admin_draft/configuration'
|
6
|
+
require 'rails_admin_draft/model'
|
7
|
+
|
8
|
+
require 'rails_admin_draft/concern/draft_concern'
|
9
|
+
require 'rails_admin_draft/services/draft/clone_service'
|
10
|
+
require 'rails_admin_draft/services/draft/overwrite_service'
|
11
|
+
|
12
|
+
require 'rails_admin_draft/rails_admin/config/actions/copy_as_draft'
|
13
|
+
require 'rails_admin_draft/rails_admin/config/actions/edit'
|
14
|
+
require 'rails_admin_draft/rails_admin/config/actions/new'
|
15
|
+
|
16
|
+
load 'rails_admin_draft/engine.rb'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_admin_draft
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Grégory Huet
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-01
|
12
|
+
date: 2023-02-01 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -176,6 +176,21 @@ files:
|
|
176
176
|
- app/assets/stylesheets/rails_admin/rails_admin_draft.css
|
177
177
|
- app/views/rails_admin/main/_submit_buttons.html.erb
|
178
178
|
- app/views/rails_admin/main/edit.html.erb
|
179
|
+
- config/initializers/draft_status.rb
|
180
|
+
- config/locales/rails_admin_draft/en.yml
|
181
|
+
- config/locales/rails_admin_draft/fr.yml
|
182
|
+
- lib/generators/draft_migration_generator.rb
|
183
|
+
- lib/rails_admin_draft.rb
|
184
|
+
- lib/rails_admin_draft/concern/draft_concern.rb
|
185
|
+
- lib/rails_admin_draft/configuration.rb
|
186
|
+
- lib/rails_admin_draft/engine.rb
|
187
|
+
- lib/rails_admin_draft/model.rb
|
188
|
+
- lib/rails_admin_draft/rails_admin/config/actions/copy_as_draft.rb
|
189
|
+
- lib/rails_admin_draft/rails_admin/config/actions/edit.rb
|
190
|
+
- lib/rails_admin_draft/rails_admin/config/actions/new.rb
|
191
|
+
- lib/rails_admin_draft/services/draft/clone_service.rb
|
192
|
+
- lib/rails_admin_draft/services/draft/overwrite_service.rb
|
193
|
+
- lib/rails_admin_draft/version.rb
|
179
194
|
homepage: https://github.com/ixmedia/rails_admin_draft
|
180
195
|
licenses:
|
181
196
|
- MIT
|