attach 1.1.2 → 2.0.3
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/db/migrate/20170301120000_create_attachment_tables.rb +15 -9
- data/lib/attach/attachment.rb +123 -66
- data/lib/attach/attachment_binary.rb +3 -3
- data/lib/attach/attachment_dsl.rb +4 -5
- data/lib/attach/backends/abstract.rb +6 -4
- data/lib/attach/backends/database.rb +12 -20
- data/lib/attach/backends/file_system.rb +15 -9
- data/lib/attach/blob_types/file.rb +28 -0
- data/lib/attach/blob_types/raw.rb +25 -0
- data/lib/attach/file.rb +4 -4
- data/lib/attach/middleware.rb +23 -14
- data/lib/attach/model_extension/class_methods.rb +76 -0
- data/lib/attach/model_extension/inclusion.rb +93 -0
- data/lib/attach/model_extension/instance_methods.rb +54 -0
- data/lib/attach/model_extension.rb +8 -184
- data/lib/attach/processor.rb +26 -25
- data/lib/attach/railtie.rb +4 -4
- data/lib/attach/version.rb +5 -1
- data/lib/attach.rb +15 -21
- metadata +24 -7
- data/db/migrate/20170314113014_add_custom_data_to_attachments.rb +0 -15
- data/db/migrate/20181123160000_add_index_to_attachment_binaries.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67e480487c042fe887ff3c518fc372df0e9ed21890f25df6b15b7ac408ff63f9
|
4
|
+
data.tar.gz: 046166beef6d41bea1fe55927d5006ab7512aa57a2201b15abfcc8cca94a8832
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b8b1e30d31ef5782738cd01e4bc4ce6528472e014a77e7ef1e8be80407f787390be4cf422e7eee13f6baed150591fbc1e68a4a1f968a85a4109901d2d50e8c65
|
7
|
+
data.tar.gz: 8d74cb792d7a9981ca063cc1c9b0b667f2419d36368c600e56bfdb9f351b426d3fa49b0cdbbca98447d8086c0a287f194c2b2d03437d2a3a699a9669acc8c119
|
@@ -1,18 +1,23 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateAttachmentTables < ActiveRecord::Migration[6.0]
|
4
|
+
|
2
5
|
def up
|
3
6
|
create_table :attachments do |t|
|
4
|
-
t.
|
5
|
-
t.string
|
6
|
-
t.
|
7
|
-
t.
|
8
|
-
t.boolean :processed, :
|
7
|
+
t.belongs_to :owner, polymorphic: true
|
8
|
+
t.string :token, :digest, :role, :type, :file_name, :file_type, :cache_type, :cache_max_age, :disposition
|
9
|
+
t.bigint :file_size
|
10
|
+
t.belongs_to :parent
|
11
|
+
t.boolean :processed, default: false
|
12
|
+
t.text :custom
|
13
|
+
t.boolean :serve, default: false
|
9
14
|
t.timestamps
|
10
|
-
t.index :
|
15
|
+
t.index :token, length: 16
|
11
16
|
end
|
12
17
|
|
13
18
|
create_table :attachment_binaries do |t|
|
14
|
-
t.
|
15
|
-
t.binary :data, :
|
19
|
+
t.belongs_to :attachment
|
20
|
+
t.binary :data, limit: 10_485_760
|
16
21
|
t.timestamps
|
17
22
|
end
|
18
23
|
end
|
@@ -21,4 +26,5 @@ class CreateAttachmentTables < ActiveRecord::Migration
|
|
21
26
|
drop_table :attachments
|
22
27
|
drop_table :attachment_binaries
|
23
28
|
end
|
29
|
+
|
24
30
|
end
|
data/lib/attach/attachment.rb
CHANGED
@@ -1,116 +1,173 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'securerandom'
|
2
4
|
require 'digest/sha1'
|
3
5
|
require 'attach/attachment_binary'
|
6
|
+
require 'attach/processor'
|
7
|
+
require 'attach/blob_types/raw'
|
8
|
+
require 'attach/blob_types/file'
|
4
9
|
|
5
10
|
module Attach
|
6
11
|
class Attachment < ActiveRecord::Base
|
7
12
|
|
8
|
-
# Set the table name
|
9
13
|
self.table_name = 'attachments'
|
10
14
|
self.inheritance_column = 'sti_type'
|
11
15
|
|
12
|
-
|
13
|
-
# by the class on save.
|
14
|
-
attr_writer :binary
|
16
|
+
attr_writer :backend
|
15
17
|
|
16
|
-
|
17
|
-
belongs_to :
|
18
|
-
|
19
|
-
has_many :children, :class_name => 'Attach::Attachment', :dependent => :destroy, :foreign_key => :parent_id
|
18
|
+
belongs_to :owner, polymorphic: true
|
19
|
+
belongs_to :parent, class_name: 'Attach::Attachment', optional: true
|
20
|
+
has_many :children, class_name: 'Attach::Attachment', dependent: :destroy, foreign_key: :parent_id
|
20
21
|
|
21
|
-
|
22
|
-
validates :
|
23
|
-
validates :
|
24
|
-
validates :
|
25
|
-
validates :
|
26
|
-
validates :token, :presence => true, :uniqueness => true
|
22
|
+
validates :file_name, presence: true
|
23
|
+
validates :file_type, presence: true
|
24
|
+
validates :file_size, presence: true
|
25
|
+
validates :digest, presence: true
|
26
|
+
validates :token, presence: true, uniqueness: { case_sensitive: false }
|
27
27
|
|
28
|
-
|
29
|
-
serialize :custom, Hash
|
28
|
+
serialize :custom, type: Hash, default: {}
|
30
29
|
|
31
|
-
|
32
|
-
before_validation
|
33
|
-
|
34
|
-
self.digest ||= self.binary.is_a?(String) ? Digest::SHA1.hexdigest(self.binary) : Attach.backend.digest(self.binary)
|
35
|
-
self.file_size ||= self.binary.is_a?(String) ? self.binary.bytesize : Attach.backend.bytesize(self.binary)
|
36
|
-
end
|
30
|
+
before_validation :set_token
|
31
|
+
before_validation :set_digest
|
32
|
+
before_validation :set_file_size
|
37
33
|
|
38
|
-
|
39
|
-
after_create
|
40
|
-
if self.binary
|
41
|
-
Attach.backend.write(self, self.binary)
|
42
|
-
end
|
43
|
-
end
|
34
|
+
after_create :write_blob_to_backend
|
35
|
+
after_create :destroy_other_attachments_for_same_parent
|
44
36
|
|
45
|
-
|
46
|
-
after_create do
|
47
|
-
self.owner.attachments.where.not(:id => self).where(:parent_id => self.parent_id, :role => self.role).destroy_all
|
48
|
-
end
|
37
|
+
after_commit :queue_or_process_with_processor
|
49
38
|
|
50
|
-
|
51
|
-
after_commit do
|
52
|
-
unless self.processed? || self.parent_id
|
53
|
-
self.processor.queue_or_process
|
54
|
-
end
|
55
|
-
end
|
39
|
+
after_destroy :remove_from_backend
|
56
40
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
end
|
41
|
+
def blob
|
42
|
+
return @blob if instance_variable_defined?('@blob')
|
43
|
+
return nil unless persisted?
|
61
44
|
|
62
|
-
|
63
|
-
def self.for(role)
|
64
|
-
self.where(:role => role).first
|
45
|
+
@blob = backend.read(self)
|
65
46
|
end
|
66
47
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
48
|
+
def blob=(blob)
|
49
|
+
unless blob.nil? || blob.is_a?(BlobTypes::File) || blob.is_a?(BlobTypes::Raw)
|
50
|
+
raise ArgumentError, 'Only nil or a File/Raw blob type can be set as a blob for an attachment'
|
51
|
+
end
|
52
|
+
|
53
|
+
@blob = blob
|
71
54
|
end
|
72
55
|
|
73
|
-
# Return the path to the attachment
|
74
56
|
def url
|
75
|
-
|
57
|
+
backend.url(self)
|
76
58
|
end
|
77
59
|
|
78
|
-
# Is the attachment an image?
|
79
60
|
def image?
|
80
61
|
file_type =~ /\Aimage\//
|
81
62
|
end
|
82
63
|
|
83
|
-
# Return a processor for this attachment
|
84
64
|
def processor
|
85
65
|
@processor ||= Processor.new(self)
|
86
66
|
end
|
87
67
|
|
88
|
-
# Return a child process
|
89
68
|
def child(role)
|
90
69
|
@cached_children ||= {}
|
91
|
-
@cached_children
|
92
|
-
|
70
|
+
if @cached_children.key?(role.to_sym)
|
71
|
+
return @cached_children[role.to_sym]
|
72
|
+
end
|
73
|
+
|
74
|
+
@cached_children[role.to_sym] = children.where(role: role).first
|
93
75
|
end
|
94
76
|
|
95
|
-
# Try to return a given otherwise revert to the parent
|
96
77
|
def try(role)
|
97
78
|
child(role) || self
|
98
79
|
end
|
99
80
|
|
100
|
-
#
|
81
|
+
# rubocop:disable Metrics/AbcSize
|
101
82
|
def add_child(role, &block)
|
102
|
-
attachment =
|
83
|
+
attachment = children.build
|
103
84
|
attachment.role = role
|
104
|
-
attachment.owner =
|
105
|
-
attachment.file_name =
|
106
|
-
attachment.file_type =
|
107
|
-
attachment.disposition =
|
108
|
-
attachment.cache_type =
|
109
|
-
attachment.cache_max_age =
|
110
|
-
attachment.
|
85
|
+
attachment.owner = owner
|
86
|
+
attachment.file_name = file_name
|
87
|
+
attachment.file_type = file_type
|
88
|
+
attachment.disposition = disposition
|
89
|
+
attachment.cache_type = cache_type
|
90
|
+
attachment.cache_max_age = cache_max_age
|
91
|
+
attachment.serve = serve
|
92
|
+
attachment.type = type
|
111
93
|
block.call(attachment)
|
112
94
|
attachment.save!
|
113
95
|
end
|
96
|
+
# rubocop:enable Metrics/AbcSize
|
97
|
+
|
98
|
+
# rubocop:disable Metrics/AbcSize
|
99
|
+
def copy_attributes_from_file(file)
|
100
|
+
case file.class.name
|
101
|
+
when 'ActionDispatch::Http::UploadedFile'
|
102
|
+
self.blob = BlobTypes::File.new(file.tempfile)
|
103
|
+
self.file_name = file.original_filename
|
104
|
+
self.file_type = file.content_type
|
105
|
+
when 'Attach::File'
|
106
|
+
self.blob = BlobTypes::Raw.new(file.data)
|
107
|
+
self.file_name = file.name
|
108
|
+
self.file_type = file.type
|
109
|
+
else
|
110
|
+
self.blob = BlobTypes::Raw.new(file)
|
111
|
+
self.file_name = 'untitled'
|
112
|
+
self.file_type = 'application/octet-stream'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
# rubocop:enable Metrics/AbcSize
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def backend
|
120
|
+
@backend || Attach.backend
|
121
|
+
end
|
122
|
+
|
123
|
+
def write_blob_to_backend
|
124
|
+
return if blob.blank?
|
125
|
+
|
126
|
+
backend.write(self, blob)
|
127
|
+
end
|
128
|
+
|
129
|
+
def destroy_other_attachments_for_same_parent
|
130
|
+
owner.attachments.where.not(id: self).where(parent_id: parent_id, role: role).destroy_all
|
131
|
+
end
|
132
|
+
|
133
|
+
def set_token
|
134
|
+
return if token.present?
|
135
|
+
|
136
|
+
self.token = SecureRandom.uuid
|
137
|
+
end
|
138
|
+
|
139
|
+
def set_digest
|
140
|
+
return if digest.present?
|
141
|
+
return if blob.blank?
|
142
|
+
|
143
|
+
self.digest = blob.digest
|
144
|
+
end
|
145
|
+
|
146
|
+
def set_file_size
|
147
|
+
return if file_size.present?
|
148
|
+
return if blob.blank?
|
149
|
+
|
150
|
+
self.file_size = blob.size
|
151
|
+
end
|
152
|
+
|
153
|
+
def remove_from_backend
|
154
|
+
backend.delete(self)
|
155
|
+
end
|
156
|
+
|
157
|
+
def queue_or_process_with_processor
|
158
|
+
return if processed?
|
159
|
+
return if parent_id
|
160
|
+
|
161
|
+
processor.queue_or_process
|
162
|
+
end
|
163
|
+
|
164
|
+
class << self
|
165
|
+
|
166
|
+
def for(role)
|
167
|
+
where(role: role).first
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
114
171
|
|
115
172
|
end
|
116
173
|
end
|
@@ -1,11 +1,11 @@
|
|
1
|
-
|
2
|
-
require 'digest/sha1'
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module Attach
|
5
4
|
class AttachmentBinary < ActiveRecord::Base
|
6
5
|
|
7
|
-
# Set the table name
|
8
6
|
self.table_name = 'attachment_binaries'
|
9
7
|
|
8
|
+
belongs_to :attachment
|
9
|
+
|
10
10
|
end
|
11
11
|
end
|
@@ -1,15 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Attach
|
2
4
|
class AttachmentDSL
|
3
5
|
|
4
|
-
attr_reader :processors
|
5
|
-
attr_reader :validators
|
6
|
+
attr_reader :processors, :validators
|
6
7
|
|
7
8
|
def initialize(&block)
|
8
9
|
@processors = []
|
9
10
|
@validators = []
|
10
|
-
if block_given?
|
11
|
-
instance_eval(&block)
|
12
|
-
end
|
11
|
+
instance_eval(&block) if block_given?
|
13
12
|
end
|
14
13
|
|
15
14
|
def processor(*processors, &block)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Attach
|
2
4
|
module Backends
|
3
5
|
class Abstract
|
@@ -7,19 +9,19 @@ module Attach
|
|
7
9
|
end
|
8
10
|
|
9
11
|
#
|
10
|
-
#
|
12
|
+
# Return the data for the given attachment
|
11
13
|
#
|
12
14
|
def read(attachment)
|
13
15
|
end
|
14
16
|
|
15
17
|
#
|
16
|
-
#
|
18
|
+
# Write data for the given attachment
|
17
19
|
#
|
18
20
|
def write(attachment, data)
|
19
21
|
end
|
20
22
|
|
21
23
|
#
|
22
|
-
#
|
24
|
+
# Delete the data for the given attachment
|
23
25
|
#
|
24
26
|
def delete(attachment)
|
25
27
|
end
|
@@ -32,7 +34,7 @@ module Attach
|
|
32
34
|
end
|
33
35
|
|
34
36
|
#
|
35
|
-
#
|
37
|
+
# Return binaries for a set of files. They should be returned as a hash consisting
|
36
38
|
# of the attachment ID followed by the data
|
37
39
|
#
|
38
40
|
def read_multi(attachments)
|
@@ -1,37 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'attach/attachment_binary'
|
1
4
|
require 'attach/backends/abstract'
|
5
|
+
require 'attach/blob_types/raw'
|
2
6
|
|
3
7
|
module Attach
|
4
8
|
module Backends
|
5
9
|
class Database < Abstract
|
6
10
|
|
7
11
|
def read(attachment)
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
end
|
12
|
+
binary = AttachmentBinary.find_by(attachment_id: attachment.id)
|
13
|
+
return if binary.nil?
|
14
|
+
|
15
|
+
BlobTypes::Raw.new(binary.data)
|
13
16
|
end
|
14
17
|
|
15
|
-
def write(attachment,
|
16
|
-
binary_object = AttachmentBinary.where(:
|
17
|
-
|
18
|
-
binary.rewind
|
19
|
-
binary_object.data = binary.read
|
20
|
-
else
|
21
|
-
binary_object.data = binary
|
22
|
-
end
|
18
|
+
def write(attachment, blob)
|
19
|
+
binary_object = AttachmentBinary.where(attachment_id: attachment.id).first_or_initialize
|
20
|
+
binary_object.data = blob.read
|
23
21
|
binary_object.save!
|
24
22
|
binary_object
|
25
23
|
end
|
26
24
|
|
27
25
|
def delete(attachment)
|
28
|
-
AttachmentBinary.where(:
|
29
|
-
end
|
30
|
-
|
31
|
-
def read_multi(attachments)
|
32
|
-
AttachmentBinary.where(:attachment_id => attachments.map(&:id)).each_with_object({}) do |binary, hash|
|
33
|
-
hash[binary.attachment_id] = binary.data
|
34
|
-
end
|
26
|
+
AttachmentBinary.where(attachment_id: attachment.id).destroy_all
|
35
27
|
end
|
36
28
|
|
37
29
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'fileutils'
|
2
4
|
require 'attach/backends/abstract'
|
3
5
|
|
@@ -6,24 +8,28 @@ module Attach
|
|
6
8
|
class FileSystem < Abstract
|
7
9
|
|
8
10
|
def read(attachment)
|
9
|
-
|
11
|
+
file = File.new(path_for_attachment(attachment))
|
12
|
+
BlobTypes::File.new(file)
|
10
13
|
end
|
11
14
|
|
12
|
-
def write(attachment,
|
15
|
+
def write(attachment, blob)
|
13
16
|
path = path_for_attachment(attachment)
|
14
17
|
FileUtils.mkdir_p(::File.dirname(path))
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
f.write(data)
|
20
|
-
end
|
18
|
+
|
19
|
+
if blob.is_a?(BlobTypes::File)
|
20
|
+
FileUtils.mv(blob.file.path, path)
|
21
|
+
return path
|
21
22
|
end
|
23
|
+
|
24
|
+
::File.binwrite(path, blob.read)
|
25
|
+
|
26
|
+
path
|
22
27
|
end
|
23
28
|
|
24
29
|
def delete(attachment)
|
25
30
|
path = path_for_attachment(attachment)
|
26
31
|
FileUtils.rm(path) if ::File.file?(path)
|
32
|
+
path
|
27
33
|
end
|
28
34
|
|
29
35
|
private
|
@@ -33,7 +39,7 @@ module Attach
|
|
33
39
|
end
|
34
40
|
|
35
41
|
def path_for_attachment(attachment)
|
36
|
-
::File.join(root_dir, attachment.token[0,2], attachment.token[2,2], attachment.token[4,40])
|
42
|
+
::File.join(root_dir, attachment.token[0, 2], attachment.token[2, 2], attachment.token[4, 40])
|
37
43
|
end
|
38
44
|
|
39
45
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Attach
|
4
|
+
module BlobTypes
|
5
|
+
class File
|
6
|
+
|
7
|
+
attr_reader :file
|
8
|
+
|
9
|
+
def initialize(file)
|
10
|
+
@file = file
|
11
|
+
end
|
12
|
+
|
13
|
+
def read
|
14
|
+
@file.rewind
|
15
|
+
@file.read
|
16
|
+
end
|
17
|
+
|
18
|
+
def size
|
19
|
+
@file.size
|
20
|
+
end
|
21
|
+
|
22
|
+
def digest
|
23
|
+
Digest::SHA1.file(@file.path).to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Attach
|
4
|
+
module BlobTypes
|
5
|
+
class Raw
|
6
|
+
|
7
|
+
def initialize(data)
|
8
|
+
@data = data
|
9
|
+
end
|
10
|
+
|
11
|
+
def read
|
12
|
+
@data
|
13
|
+
end
|
14
|
+
|
15
|
+
def size
|
16
|
+
@data.bytesize
|
17
|
+
end
|
18
|
+
|
19
|
+
def digest
|
20
|
+
Digest::SHA1.hexdigest(@data)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/attach/file.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Attach
|
2
4
|
class File
|
3
5
|
|
4
|
-
attr_accessor :data
|
5
|
-
attr_accessor :name
|
6
|
-
attr_accessor :type
|
6
|
+
attr_accessor :data, :name, :type
|
7
7
|
|
8
|
-
def initialize(data, name =
|
8
|
+
def initialize(data, name = 'untitled', type = 'application/octet-stream')
|
9
9
|
@data = data
|
10
10
|
@name = name
|
11
11
|
@type = type
|
data/lib/attach/middleware.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'attach/attachment'
|
2
4
|
|
3
5
|
module Attach
|
@@ -8,21 +10,28 @@ module Attach
|
|
8
10
|
end
|
9
11
|
|
10
12
|
def call(env)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
},
|
19
|
-
[attachment.binary]]
|
20
|
-
else
|
21
|
-
[404, {}, ["Attachment not found"]]
|
22
|
-
end
|
23
|
-
else
|
24
|
-
@app.call(env)
|
13
|
+
unless env['PATH_INFO'] =~ /\A\/attachment\/([a-f0-9-]{36})\/(.*)/
|
14
|
+
return @app.call(env)
|
15
|
+
end
|
16
|
+
|
17
|
+
attachment = Attach::Attachment.where(serve: true).find_by(token: Regexp.last_match(1))
|
18
|
+
if attachment.nil?
|
19
|
+
return [404, {}, ['Attachment not found']]
|
25
20
|
end
|
21
|
+
|
22
|
+
[200, headers_for_attachment(attachment), [attachment.blob.read]]
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def headers_for_attachment(attachment)
|
28
|
+
max_age = attachment.cache_max_age || 30.days.to_i
|
29
|
+
{
|
30
|
+
'Content-Length' => attachment.file_size.to_s,
|
31
|
+
'Content-Type' => attachment.file_type,
|
32
|
+
'Cache-Control' => "#{attachment.cache_type || 'private'}, immutable, max-age=#{max_age}",
|
33
|
+
'Content-Disposition' => "#{attachment.disposition || 'attachment'}; filename=\"#{attachment.file_name}\""
|
34
|
+
}
|
26
35
|
end
|
27
36
|
|
28
37
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'attach/attachment_dsl'
|
4
|
+
require 'attach/attachment'
|
5
|
+
require 'attach/model_extension/inclusion'
|
6
|
+
|
7
|
+
require 'records_manipulator/relation_extension'
|
8
|
+
ActiveRecord::Relation.include RecordsManipulator::RelationExtension
|
9
|
+
|
10
|
+
require 'records_manipulator/base_extension'
|
11
|
+
ActiveRecord::Base.include RecordsManipulator::BaseExtension
|
12
|
+
|
13
|
+
module Attach
|
14
|
+
module ModelExtension
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
def attachment_validators
|
18
|
+
@attachment_validators ||= {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def attachment_processors
|
22
|
+
@attachment_processors ||= {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def attachment(name, **options, &block)
|
26
|
+
setup_model
|
27
|
+
parse_dsl(name, &block)
|
28
|
+
|
29
|
+
define_method name do
|
30
|
+
get_attachment(name)
|
31
|
+
end
|
32
|
+
|
33
|
+
define_method "#{name}=" do |file|
|
34
|
+
set_attachment(name, file, **options)
|
35
|
+
end
|
36
|
+
|
37
|
+
define_method "#{name}_delete" do
|
38
|
+
instance_variable_get("@#{name}_delete")
|
39
|
+
end
|
40
|
+
|
41
|
+
define_method "#{name}_delete=" do |delete|
|
42
|
+
delete = delete.to_i
|
43
|
+
instance_variable_set("@#{name}_delete", delete)
|
44
|
+
if delete == 1
|
45
|
+
@pending_attachment_deletions ||= []
|
46
|
+
@pending_attachment_deletions << name
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def includes_attachment(*args, **options)
|
52
|
+
manipulate do |scope|
|
53
|
+
inclusion = Inclusion.new(scope, *args, **options)
|
54
|
+
inclusion.prepare
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def setup_model
|
61
|
+
return if reflect_on_all_associations(:has_many).map(&:name).include?(:attachments)
|
62
|
+
|
63
|
+
has_many :attachments, as: :owner, dependent: :destroy, class_name: 'Attach::Attachment'
|
64
|
+
validate :validate_attachments
|
65
|
+
after_save :process_pending_attachments
|
66
|
+
end
|
67
|
+
|
68
|
+
def parse_dsl(name, &block)
|
69
|
+
dsl = AttachmentDSL.new(&block)
|
70
|
+
attachment_validators[name.to_sym] = dsl.validators
|
71
|
+
attachment_processors[name.to_sym] = dsl.processors
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Attach
|
4
|
+
module ModelExtension
|
5
|
+
class Inclusion
|
6
|
+
|
7
|
+
def initialize(scope, *options)
|
8
|
+
@scope = scope
|
9
|
+
@options = options
|
10
|
+
@fields = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def prepare
|
14
|
+
return if @scope.empty?
|
15
|
+
|
16
|
+
prepare_fields
|
17
|
+
find_all_attachments
|
18
|
+
find_child_attachments
|
19
|
+
add_attachments_to_records
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def prepare_fields
|
25
|
+
@options.each do |field|
|
26
|
+
case field
|
27
|
+
when Symbol
|
28
|
+
@fields[field] = []
|
29
|
+
when Hash
|
30
|
+
field.each do |k, v|
|
31
|
+
case v
|
32
|
+
when Array
|
33
|
+
@fields.merge!(field)
|
34
|
+
when Symbol
|
35
|
+
@fields[k] = [v]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_all_attachments
|
43
|
+
@attachment_ids = []
|
44
|
+
@attachments_map = Attachment.where(
|
45
|
+
owner_id: @scope.map(&:id),
|
46
|
+
owner_type: @scope.first.class.name,
|
47
|
+
role: @fields.keys
|
48
|
+
).each_with_object({}) do |attachment, hash|
|
49
|
+
hash[attachment.owner_id] ||= {}
|
50
|
+
hash[attachment.owner_id][attachment.role] = attachment
|
51
|
+
@attachment_ids << attachment.id
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def find_child_attachments
|
56
|
+
@child_attachments = Attachment.where(
|
57
|
+
parent: @attachment_ids,
|
58
|
+
role: @fields.values.flatten.compact
|
59
|
+
).each_with_object({}) do |attachment, hash|
|
60
|
+
hash[attachment.parent_id] ||= {}
|
61
|
+
hash[attachment.parent_id][attachment.role] = attachment
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def add_attachments_to_records
|
66
|
+
@scope.each do |record|
|
67
|
+
preloaded_attachments = @attachments_map[record.id] || {}
|
68
|
+
@fields.each_key do |role|
|
69
|
+
cache_attachment(
|
70
|
+
record,
|
71
|
+
role,
|
72
|
+
preloaded_attachments[role.to_s]
|
73
|
+
)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def cache_attachment(record, role, attachment)
|
79
|
+
record.instance_variable_set("@#{role}", attachment)
|
80
|
+
return if attachment.nil?
|
81
|
+
|
82
|
+
@fields[role.to_sym].each do |child_role|
|
83
|
+
child_attachment = @child_attachments.dig(attachment.id, child_role.to_s)
|
84
|
+
if attachment.instance_variable_get('@cached_children').nil?
|
85
|
+
attachment.instance_variable_set('@cached_children', {})
|
86
|
+
end
|
87
|
+
attachment.instance_variable_get('@cached_children')[child_role] = child_attachment
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'attach/attachment'
|
4
|
+
|
5
|
+
module Attach
|
6
|
+
module ModelExtension
|
7
|
+
module InstanceMethods
|
8
|
+
|
9
|
+
def process_pending_attachments
|
10
|
+
attachments.where(role: @pending_attachment_deletions).destroy_all if @pending_attachment_deletions
|
11
|
+
|
12
|
+
return if @pending_attachments.nil? || @pending_attachments.empty?
|
13
|
+
|
14
|
+
@pending_attachments.each_value(&:save!)
|
15
|
+
@pending_attachments = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def get_attachment(name)
|
21
|
+
iv_name = "@#{name}"
|
22
|
+
return instance_variable_get(iv_name) if instance_variable_defined?(iv_name)
|
23
|
+
|
24
|
+
if attachment = attachments.where(role: name, parent_id: nil).first
|
25
|
+
return instance_variable_set(iv_name, attachment)
|
26
|
+
end
|
27
|
+
|
28
|
+
instance_variable_set(iv_name, nil)
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_attachment(name, file, **options)
|
32
|
+
attachment = Attachment.new({ owner: self, role: name }.merge(options))
|
33
|
+
attachment.copy_attributes_from_file(file)
|
34
|
+
@pending_attachments ||= {}
|
35
|
+
@pending_attachments[name] = attachment
|
36
|
+
instance_variable_set("@#{name}", attachment)
|
37
|
+
end
|
38
|
+
|
39
|
+
def validate_attachments
|
40
|
+
return if @pending_attachments.nil? || @pending_attachments.empty?
|
41
|
+
|
42
|
+
@pending_attachments.each_value do |attachment|
|
43
|
+
validators = self.class.attachment_validators[attachment.role.to_sym]
|
44
|
+
next if validators.blank?
|
45
|
+
|
46
|
+
validators.each do |validator|
|
47
|
+
validator.call(attachment, errors)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -1,192 +1,16 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'attach/
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'attach/model_extension/class_methods'
|
4
|
+
require 'attach/model_extension/instance_methods'
|
4
5
|
|
5
6
|
module Attach
|
6
7
|
module ModelExtension
|
7
8
|
|
8
|
-
|
9
|
-
base.extend ClassMethods
|
10
|
-
base.after_save do
|
11
|
-
if @pending_attachment_deletions
|
12
|
-
self.attachments.where(:role => @pending_attachment_deletions).destroy_all
|
13
|
-
end
|
14
|
-
|
15
|
-
@replaced_attachment&.destroy
|
16
|
-
|
17
|
-
if @pending_attachments
|
18
|
-
@pending_attachments.values.each(&:save!)
|
19
|
-
@pending_attachments = nil
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
module ClassMethods
|
25
|
-
|
26
|
-
def includes_attachments(*options)
|
27
|
-
manipulate do |records|
|
28
|
-
if records.empty?
|
29
|
-
# Nothing to do
|
30
|
-
else
|
31
|
-
|
32
|
-
if options.first.is_a?(Hash)
|
33
|
-
options = options.first
|
34
|
-
binaries_to_include = options.delete(:_include_binaries) || {}
|
35
|
-
else
|
36
|
-
binaries_to_include = {}
|
37
|
-
options = options.each_with_object({}) do |opt, hash|
|
38
|
-
if opt.is_a?(Symbol) || opt.is_a?(String)
|
39
|
-
hash[opt.to_sym] = []
|
40
|
-
elsif opt.is_a?(Hash)
|
41
|
-
opt.each do |key, value|
|
42
|
-
if key == :_include_binaries
|
43
|
-
binaries_to_include = value
|
44
|
-
else
|
45
|
-
hash[key.to_sym] = value
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|
52
|
-
|
53
|
-
options.keys.each do |key|
|
54
|
-
if options[key].is_a?(Symbol)
|
55
|
-
options[key] = [options[key]]
|
56
|
-
elsif options[key] == true
|
57
|
-
options[key] = []
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
attachments_for_binary_preload = []
|
62
|
-
root_attachments = {}
|
63
|
-
Attachment.where(:owner_id => records.map(&:id), :owner_type => records.first.class.to_s, :role => options.keys).each do |attachment|
|
64
|
-
root_attachments[[attachment.owner_id, attachment.role]] = attachment
|
65
|
-
if binaries_to_include[attachment.role.to_sym] && binaries_to_include[attachment.role.to_sym].include?(:_self)
|
66
|
-
attachments_for_binary_preload << attachment
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
child_roles = options.values.flatten
|
71
|
-
unless child_roles.empty?
|
72
|
-
child_attachments = {}
|
73
|
-
Attachment.where(:parent_id => root_attachments.values.map(&:id), :role => child_roles).each do |attachment|
|
74
|
-
child_attachments[[attachment.parent_id, attachment.role]] = attachment
|
75
|
-
end
|
76
|
-
|
77
|
-
root_attachments.values.each do |attachment|
|
78
|
-
options[attachment.role.to_sym].each do |role|
|
79
|
-
child_attachment = child_attachments[[attachment.id, role.to_s]]
|
80
|
-
|
81
|
-
if child_attachment && binaries_to_include[attachment.role.to_sym] && binaries_to_include[attachment.role.to_sym].include?(role)
|
82
|
-
attachments_for_binary_preload << child_attachment
|
83
|
-
end
|
84
|
-
|
85
|
-
attachment.instance_variable_set("@cached_children", {}) if attachment.instance_variable_get("@cached_children").nil?
|
86
|
-
attachment.instance_variable_get("@cached_children")[role.to_sym] = child_attachments[[attachment.id, role.to_s]] || :nil
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
if binaries = Attach.backend.read_multi(attachments_for_binary_preload)
|
92
|
-
attachments_for_binary_preload.each do |attachment|
|
93
|
-
attachment.instance_variable_set("@binary", binaries[attachment.id] || :nil)
|
94
|
-
end
|
95
|
-
else
|
96
|
-
# Preloading binaries isn't supported by the backend
|
97
|
-
end
|
98
|
-
|
99
|
-
records.each do |record|
|
100
|
-
options.keys.each do |role|
|
101
|
-
record.instance_variable_set("@#{role}", root_attachments[[record.id, role.to_s]] || :nil)
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def attachment(name, options = {}, &block)
|
109
|
-
unless self.reflect_on_all_associations(:has_many).map(&:name).include?(:attachments)
|
110
|
-
has_many :attachments, :as => :owner, :dependent => :destroy, :class_name => 'Attach::Attachment'
|
111
|
-
end
|
112
|
-
|
113
|
-
dsl = AttachmentDSL.new(&block)
|
114
|
-
|
115
|
-
dsl.processors.each do |processor|
|
116
|
-
Processor.register(self, name, &processor)
|
117
|
-
end
|
118
|
-
|
119
|
-
if dsl.validators.size > 0
|
120
|
-
validate do
|
121
|
-
attachment = @pending_attachments && @pending_attachments[name] ? @pending_attachments[name] : send(name)
|
122
|
-
file_errors = []
|
123
|
-
dsl.validators.each do |validator|
|
124
|
-
validator.call(attachment, file_errors)
|
125
|
-
end
|
126
|
-
file_errors.each { |e| errors.add("#{name}_file", e) }
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
define_method name do
|
131
|
-
var = instance_variable_get("@#{name}")
|
132
|
-
if var
|
133
|
-
var == :nil ? nil : var
|
134
|
-
else
|
135
|
-
if attachment = self.attachments.where(:role => name, :parent_id => nil).first
|
136
|
-
instance_variable_set("@#{name}", attachment)
|
137
|
-
else
|
138
|
-
instance_variable_set("@#{name}", :nil)
|
139
|
-
nil
|
140
|
-
end
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
define_method "#{name}=" do |file|
|
145
|
-
if file.is_a?(Attach::Attachment)
|
146
|
-
@replaced_attachment = self.try(name)
|
147
|
-
attachment = file
|
148
|
-
attachment.owner = self
|
149
|
-
attachment.role = name
|
150
|
-
attachment.processed = false
|
151
|
-
elsif file
|
152
|
-
attachment = Attachment.new({:owner => self, :role => name}.merge(options))
|
153
|
-
case file
|
154
|
-
when ActionDispatch::Http::UploadedFile
|
155
|
-
attachment.binary = file.tempfile
|
156
|
-
attachment.file_name = file.original_filename
|
157
|
-
attachment.file_type = file.content_type
|
158
|
-
when Attach::File
|
159
|
-
attachment.binary = file.data
|
160
|
-
attachment.file_name = file.name
|
161
|
-
attachment.file_type = file.type
|
162
|
-
else
|
163
|
-
attachment.binary = file
|
164
|
-
attachment.file_name = "untitled"
|
165
|
-
attachment.file_type = "application/octet-stream"
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
if attachment
|
170
|
-
@pending_attachments ||= {}
|
171
|
-
@pending_attachments[name] = attachment
|
172
|
-
end
|
173
|
-
instance_variable_set("@#{name}", attachment)
|
174
|
-
end
|
175
|
-
|
176
|
-
define_method "#{name}_delete" do
|
177
|
-
instance_variable_get("@#{name}_delete")
|
178
|
-
end
|
179
|
-
|
180
|
-
define_method "#{name}_delete=" do |delete|
|
181
|
-
delete = delete.to_i
|
182
|
-
instance_variable_set("@#{name}_delete", delete)
|
183
|
-
if delete == 1
|
184
|
-
@pending_attachment_deletions ||= []
|
185
|
-
@pending_attachment_deletions << name
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
9
|
+
extend ActiveSupport::Concern
|
189
10
|
|
11
|
+
included do
|
12
|
+
extend ClassMethods
|
13
|
+
include InstanceMethods
|
190
14
|
end
|
191
15
|
|
192
16
|
end
|
data/lib/attach/processor.rb
CHANGED
@@ -1,49 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Attach
|
2
4
|
class Processor
|
3
5
|
|
4
|
-
|
5
|
-
@background_block = block
|
6
|
-
end
|
6
|
+
class << self
|
7
7
|
|
8
|
-
|
9
|
-
@background_block
|
10
|
-
end
|
8
|
+
attr_reader :background_block
|
11
9
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
@processors[[model.to_s, attribute.to_s]] = block
|
16
|
-
end
|
10
|
+
def background(&block)
|
11
|
+
@background_block = block
|
12
|
+
end
|
17
13
|
|
18
|
-
def self.processor(model, attribute)
|
19
|
-
@processors && @processors[[model.to_s, attribute.to_s]]
|
20
14
|
end
|
21
|
-
|
22
15
|
def initialize(attachment)
|
23
16
|
@attachment = attachment
|
24
17
|
end
|
25
18
|
|
26
19
|
def process
|
27
|
-
call_processors
|
28
|
-
|
29
|
-
@attachment.save(:validate => false)
|
20
|
+
call_processors
|
21
|
+
mark_as_processed
|
30
22
|
end
|
31
23
|
|
32
24
|
def queue_or_process
|
33
|
-
if self.class.background_block
|
34
|
-
|
35
|
-
|
36
|
-
process
|
37
|
-
end
|
25
|
+
return self.class.background_block.call(@attachment) if self.class.background_block
|
26
|
+
|
27
|
+
process
|
38
28
|
end
|
39
29
|
|
40
30
|
private
|
41
31
|
|
42
|
-
def call_processors
|
43
|
-
if
|
44
|
-
|
32
|
+
def call_processors
|
33
|
+
return if @attachment.role.blank?
|
34
|
+
return if @attachment.owner.nil?
|
35
|
+
|
36
|
+
processors = @attachment.owner.class.attachment_processors[@attachment.role.to_sym]
|
37
|
+
return if processors.nil? || processors.empty?
|
38
|
+
|
39
|
+
processors.each do |processor|
|
40
|
+
processor.call(@attachment)
|
45
41
|
end
|
46
42
|
end
|
47
43
|
|
44
|
+
def mark_as_processed
|
45
|
+
@attachment.processed = true
|
46
|
+
@attachment.save!
|
47
|
+
end
|
48
|
+
|
48
49
|
end
|
49
50
|
end
|
data/lib/attach/railtie.rb
CHANGED
@@ -1,18 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Attach
|
2
|
-
class Railtie < Rails::Engine
|
4
|
+
class Railtie < Rails::Engine # :nodoc:
|
3
5
|
|
4
6
|
engine_name 'attach'
|
5
7
|
|
6
8
|
initializer 'attach.initialize' do |app|
|
7
|
-
|
8
9
|
require 'attach/middleware'
|
9
10
|
app.config.middleware.use Attach::Middleware
|
10
11
|
|
11
12
|
ActiveSupport.on_load(:active_record) do
|
12
13
|
require 'attach/model_extension'
|
13
|
-
::ActiveRecord::Base.
|
14
|
+
::ActiveRecord::Base.include Attach::ModelExtension
|
14
15
|
end
|
15
|
-
|
16
16
|
end
|
17
17
|
|
18
18
|
end
|
data/lib/attach/version.rb
CHANGED
data/lib/attach.rb
CHANGED
@@ -1,32 +1,26 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'attach/file'
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
4
3
|
require 'attach/railtie' if defined?(Rails)
|
5
4
|
|
6
5
|
module Attach
|
7
6
|
|
8
|
-
|
9
|
-
@backend ||= begin
|
10
|
-
require 'attach/backends/database'
|
11
|
-
Attach::Backends::Database.new
|
12
|
-
end
|
13
|
-
end
|
7
|
+
class << self
|
14
8
|
|
15
|
-
|
16
|
-
|
17
|
-
end
|
9
|
+
attr_writer :backend
|
10
|
+
attr_accessor :asset_host
|
18
11
|
|
19
|
-
|
20
|
-
|
21
|
-
|
12
|
+
def backend
|
13
|
+
@backend ||= begin
|
14
|
+
require 'attach/backends/database'
|
15
|
+
Attach::Backends::Database.new
|
16
|
+
end
|
17
|
+
end
|
22
18
|
|
23
|
-
|
24
|
-
|
25
|
-
|
19
|
+
def use_filesystem!(config = {})
|
20
|
+
require 'attach/backends/file_system'
|
21
|
+
@backend = Attach::Backends::FileSystem.new(config)
|
22
|
+
end
|
26
23
|
|
27
|
-
def self.use_filesystem!(config = {})
|
28
|
-
require 'attach/backends/file_system'
|
29
|
-
@backend = Attach::Backends::FileSystem.new(config)
|
30
24
|
end
|
31
25
|
|
32
26
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attach
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Cooke
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-11-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '6.0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: records_manipulator
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,8 +52,6 @@ extensions: []
|
|
38
52
|
extra_rdoc_files: []
|
39
53
|
files:
|
40
54
|
- db/migrate/20170301120000_create_attachment_tables.rb
|
41
|
-
- db/migrate/20170314113014_add_custom_data_to_attachments.rb
|
42
|
-
- db/migrate/20181123160000_add_index_to_attachment_binaries.rb
|
43
55
|
- lib/attach.rb
|
44
56
|
- lib/attach/attachment.rb
|
45
57
|
- lib/attach/attachment_binary.rb
|
@@ -47,13 +59,18 @@ files:
|
|
47
59
|
- lib/attach/backends/abstract.rb
|
48
60
|
- lib/attach/backends/database.rb
|
49
61
|
- lib/attach/backends/file_system.rb
|
62
|
+
- lib/attach/blob_types/file.rb
|
63
|
+
- lib/attach/blob_types/raw.rb
|
50
64
|
- lib/attach/file.rb
|
51
65
|
- lib/attach/middleware.rb
|
52
66
|
- lib/attach/model_extension.rb
|
67
|
+
- lib/attach/model_extension/class_methods.rb
|
68
|
+
- lib/attach/model_extension/inclusion.rb
|
69
|
+
- lib/attach/model_extension/instance_methods.rb
|
53
70
|
- lib/attach/processor.rb
|
54
71
|
- lib/attach/railtie.rb
|
55
72
|
- lib/attach/version.rb
|
56
|
-
homepage: https://github.com/
|
73
|
+
homepage: https://github.com/krystal/attach
|
57
74
|
licenses:
|
58
75
|
- MIT
|
59
76
|
metadata: {}
|
@@ -65,14 +82,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
65
82
|
requirements:
|
66
83
|
- - ">="
|
67
84
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
85
|
+
version: '2.6'
|
69
86
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
87
|
requirements:
|
71
88
|
- - ">="
|
72
89
|
- !ruby/object:Gem::Version
|
73
90
|
version: '0'
|
74
91
|
requirements: []
|
75
|
-
rubygems_version: 3.
|
92
|
+
rubygems_version: 3.4.10
|
76
93
|
signing_key:
|
77
94
|
specification_version: 4
|
78
95
|
summary: Attach documents & files to Active Record models
|
@@ -1,15 +0,0 @@
|
|
1
|
-
class AddCustomDataToAttachments < ActiveRecord::Migration
|
2
|
-
|
3
|
-
def up
|
4
|
-
add_column :attachments, :custom, :text
|
5
|
-
add_column :attachments, :serve, :boolean, :default => true
|
6
|
-
add_index :attachments, :token, :length => 10
|
7
|
-
end
|
8
|
-
|
9
|
-
def down
|
10
|
-
remove_index :attachments, :token
|
11
|
-
remove_column :attachments, :custom
|
12
|
-
remove_column :attachments, :serve
|
13
|
-
end
|
14
|
-
|
15
|
-
end
|