forest_admin_rails 1.26.3 → 1.27.0
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/lib/forest_admin/types.rb +11 -0
- data/lib/forest_admin_rails/plugins/active_storage.rb +130 -0
- data/lib/forest_admin_rails/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3104918c786deec910c0eb28a201002eb3181ae7e7d322891b1319870caf7283
|
|
4
|
+
data.tar.gz: 33923c4d8bf895af848bfa63dc24c0d425623e85736f8f0176251b2142c4d9d0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0a00bc444b99799b50dc54d432a080c4949e665230fa48e1164c0f756e089880b013af9cba63feb0be0e32493b869068d7bab6a42e00ab2fdf1a283802632cd0
|
|
7
|
+
data.tar.gz: 0ae0572a332551eb9fe2340884cdd6ee26de9964c9dd9440a8eb9da216f9229c9d0a3a84e110204eb57b2426b3a46eedb3feb278d30d535048a42d8cefba9e50
|
data/lib/forest_admin/types.rb
CHANGED
|
@@ -128,6 +128,17 @@ module ForestAdmin
|
|
|
128
128
|
# Plugin - base class for creating custom plugins
|
|
129
129
|
Plugin = ForestAdminDatasourceCustomizer::Plugins::Plugin
|
|
130
130
|
|
|
131
|
+
# ActiveStoragePlugin - plugin to expose Active Storage attachments as File fields
|
|
132
|
+
# Lazy-loaded because Zeitwerk may not have autoloaded it yet when types.rb is required
|
|
133
|
+
def self.const_missing(name)
|
|
134
|
+
if name == :ActiveStoragePlugin
|
|
135
|
+
require 'forest_admin_rails/plugins/active_storage'
|
|
136
|
+
const_set(:ActiveStoragePlugin, ForestAdminRails::Plugins::ActiveStorage)
|
|
137
|
+
else
|
|
138
|
+
super
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
131
142
|
# ============================================
|
|
132
143
|
# Additional Context Classes
|
|
133
144
|
# ============================================
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
require 'base64'
|
|
2
|
+
require 'cgi'
|
|
3
|
+
require 'rack/mime'
|
|
4
|
+
|
|
5
|
+
module ForestAdminRails
|
|
6
|
+
module Plugins
|
|
7
|
+
class ActiveStorage < ForestAdminDatasourceCustomizer::Plugins::Plugin
|
|
8
|
+
ACTIVE_STORAGE_COLLECTIONS = %w[
|
|
9
|
+
ActiveStorage__Attachment
|
|
10
|
+
ActiveStorage__Blob
|
|
11
|
+
ActiveStorage__VariantRecord
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
def run(datasource_customizer, _collection_customizer = nil, options = {})
|
|
15
|
+
datasource_customizer.collections.each do |name, collection|
|
|
16
|
+
next if options[:only] && !options[:only].include?(name)
|
|
17
|
+
next if options[:except]&.include?(name)
|
|
18
|
+
|
|
19
|
+
add_attachment_fields(collection, options)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
remove_active_storage_collections(datasource_customizer) if options.fetch(:hide_internal_collections, true)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def remove_active_storage_collections(datasource_customizer)
|
|
28
|
+
ACTIVE_STORAGE_COLLECTIONS.each do |collection_name|
|
|
29
|
+
next unless datasource_customizer.collections.key?(collection_name)
|
|
30
|
+
|
|
31
|
+
datasource_customizer.remove_collection(collection_name)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def add_attachment_fields(collection, options)
|
|
36
|
+
model_class = find_model_class(collection)
|
|
37
|
+
return unless model_class
|
|
38
|
+
return unless defined?(::ActiveStorage) && model_class.respond_to?(:reflect_on_all_attachments)
|
|
39
|
+
|
|
40
|
+
model_class.reflect_on_all_attachments
|
|
41
|
+
.select { |a| a.macro == :has_one_attached }
|
|
42
|
+
.each do |attachment|
|
|
43
|
+
name = attachment.name.to_s
|
|
44
|
+
hide_attachment_relations(collection, name)
|
|
45
|
+
add_file_field(collection, model_class, name, options.fetch(:download_images_on_list, false))
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def hide_attachment_relations(collection, attachment_name)
|
|
50
|
+
%W[#{attachment_name}_attachment #{attachment_name}_blob].each do |relation_name|
|
|
51
|
+
next unless collection.schema[:fields].key?(relation_name)
|
|
52
|
+
|
|
53
|
+
collection.remove_field(relation_name)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def add_file_field(collection, model_class, attachment_name, download_images_on_list)
|
|
58
|
+
return if collection.schema[:fields][attachment_name]
|
|
59
|
+
|
|
60
|
+
collection.add_field(attachment_name,
|
|
61
|
+
ForestAdminDatasourceCustomizer::Decorators::Computed::ComputedDefinition.new(
|
|
62
|
+
column_type: 'File',
|
|
63
|
+
dependencies: ['id'],
|
|
64
|
+
values: lambda { |records, _ctx|
|
|
65
|
+
compute_values(records, model_class, attachment_name, download_images_on_list)
|
|
66
|
+
}
|
|
67
|
+
))
|
|
68
|
+
|
|
69
|
+
collection.replace_field_writing(attachment_name) do |value, context|
|
|
70
|
+
handle_write(value, context, model_class, attachment_name)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def compute_values(records, model_class, attachment_name, download_images_on_list)
|
|
75
|
+
ids = records.map { |r| r['id'] }
|
|
76
|
+
models = model_class
|
|
77
|
+
.where(id: ids)
|
|
78
|
+
.includes(:"#{attachment_name}_attachment", :"#{attachment_name}_blob")
|
|
79
|
+
.index_by(&:id)
|
|
80
|
+
|
|
81
|
+
records.map do |record|
|
|
82
|
+
model = models[record['id']]
|
|
83
|
+
next nil unless model&.public_send(attachment_name)&.attached?
|
|
84
|
+
|
|
85
|
+
blob = model.public_send(attachment_name).blob
|
|
86
|
+
|
|
87
|
+
if records.length == 1 || (download_images_on_list && blob.content_type&.start_with?('image/'))
|
|
88
|
+
content = blob.download
|
|
89
|
+
"data:#{blob.content_type};name=#{CGI.escape(blob.filename.to_s)};base64,#{Base64.strict_encode64(content)}"
|
|
90
|
+
else
|
|
91
|
+
"data:#{blob.content_type};name=#{CGI.escape(blob.filename.to_s)};base64,"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def handle_write(value, context, model_class, attachment_name)
|
|
97
|
+
record_id = context.filter&.condition_tree&.value
|
|
98
|
+
return {} unless record_id
|
|
99
|
+
|
|
100
|
+
record = model_class.find(record_id)
|
|
101
|
+
|
|
102
|
+
if value.nil? || value.to_s.strip.empty?
|
|
103
|
+
record.public_send(attachment_name).purge if record.public_send(attachment_name).attached?
|
|
104
|
+
else
|
|
105
|
+
parsed = ForestAdminAgent::Utils::Schema::ForestValueConverter.parse_data_uri(value)
|
|
106
|
+
if parsed
|
|
107
|
+
fallback_extension = Rack::Mime::MIME_TYPES.invert[parsed['mime_type']] || '.bin'
|
|
108
|
+
record.public_send(attachment_name).attach(
|
|
109
|
+
io: StringIO.new(parsed['buffer']),
|
|
110
|
+
filename: parsed['name'] || "#{attachment_name}#{fallback_extension}",
|
|
111
|
+
content_type: parsed['mime_type']
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
{}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def find_model_class(collection)
|
|
119
|
+
current = collection.respond_to?(:collection) ? collection.collection : collection
|
|
120
|
+
loop do
|
|
121
|
+
return current.model if current.respond_to?(:model) && current.model.is_a?(Class)
|
|
122
|
+
break unless current.respond_to?(:child_collection)
|
|
123
|
+
|
|
124
|
+
current = current.child_collection
|
|
125
|
+
end
|
|
126
|
+
nil
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: forest_admin_rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.27.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matthieu
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2026-
|
|
12
|
+
date: 2026-04-01 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: base64
|
|
@@ -185,6 +185,7 @@ files:
|
|
|
185
185
|
- lib/forest_admin/types.rb
|
|
186
186
|
- lib/forest_admin_rails.rb
|
|
187
187
|
- lib/forest_admin_rails/engine.rb
|
|
188
|
+
- lib/forest_admin_rails/plugins/active_storage.rb
|
|
188
189
|
- lib/forest_admin_rails/version.rb
|
|
189
190
|
- lib/generators/forest_admin_rails/install_generator.rb
|
|
190
191
|
- lib/generators/forest_admin_rails/templates/create_agent.rb
|