attach 1.0.0 → 1.0.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/lib/attach.rb +9 -0
- data/lib/attach/attachment.rb +12 -12
- data/lib/attach/attachment_dsl.rb +26 -0
- data/lib/attach/backends/abstract.rb +11 -1
- data/lib/attach/backends/database.rb +6 -0
- data/lib/attach/file.rb +15 -0
- data/lib/attach/middleware.rb +2 -2
- data/lib/attach/model_extension.rb +97 -36
- data/lib/attach/processor.rb +2 -1
- data/lib/attach/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97d4588d2fde59f28aa8079b2ebc8d423c200e3d
|
4
|
+
data.tar.gz: 98b5176bb728e78f37b0193d0934916b98162e63
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b1fb19b9cc73ac94e525b962c7212bcbff13bc7cad628c184235d1992f00e16c9963e0377839ebfb0d38f744832a1c59689a29ad9de7c89cd65022596798aaef
|
7
|
+
data.tar.gz: 9bc6875dfa3e5d8017872ec0924b588791f70194fa567b12ca66584cc561026e4d1c025d532d1e4b9149780cd23c5817537cc573b6ce8ee4bec2389b0b16b724
|
data/lib/attach.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'records_manipulator'
|
2
2
|
require 'attach/processor'
|
3
|
+
require 'attach/file'
|
3
4
|
require 'attach/railtie' if defined?(Rails)
|
4
5
|
|
5
6
|
module Attach
|
@@ -15,6 +16,14 @@ module Attach
|
|
15
16
|
@backend = backend
|
16
17
|
end
|
17
18
|
|
19
|
+
def self.asset_host
|
20
|
+
@asset_host
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.asset_host=(host)
|
24
|
+
@asset_host = host
|
25
|
+
end
|
26
|
+
|
18
27
|
def self.use_filesystem!(config = {})
|
19
28
|
require 'attach/backends/file_system'
|
20
29
|
@backend = Attach::Backends::FileSystem.new(config)
|
data/lib/attach/attachment.rb
CHANGED
@@ -11,7 +11,6 @@ module Attach
|
|
11
11
|
|
12
12
|
# This will be the ActionDispatch::UploadedFile object which be diseminated
|
13
13
|
# by the class on save.
|
14
|
-
attr_accessor :uploaded_file
|
15
14
|
attr_writer :binary
|
16
15
|
|
17
16
|
# Relationships
|
@@ -29,15 +28,11 @@ module Attach
|
|
29
28
|
# All attachments should have a token assigned to this
|
30
29
|
before_validation { self.token = SecureRandom.uuid if self.token.blank? }
|
31
30
|
|
32
|
-
#
|
33
|
-
|
34
|
-
before_validation do
|
35
|
-
if self.uploaded_file
|
36
|
-
self.binary = self.uploaded_file.tempfile.read
|
37
|
-
self.file_name = self.uploaded_file.original_filename
|
38
|
-
self.file_type = self.uploaded_file.content_type
|
39
|
-
end
|
31
|
+
# Allow custom data to be stored on the attachment
|
32
|
+
serialize :custom, Hash
|
40
33
|
|
34
|
+
# Set size and digest
|
35
|
+
before_validation do
|
41
36
|
self.digest = Digest::SHA1.hexdigest(self.binary)
|
42
37
|
self.file_size = self.binary.bytesize
|
43
38
|
end
|
@@ -72,6 +67,7 @@ module Attach
|
|
72
67
|
# Return the binary data for this attachment
|
73
68
|
def binary
|
74
69
|
@binary ||= persisted? ? Attach.backend.read(self) : nil
|
70
|
+
@binary == :nil ? nil : @binary
|
75
71
|
end
|
76
72
|
|
77
73
|
# Return the path to the attachment
|
@@ -92,9 +88,13 @@ module Attach
|
|
92
88
|
# Return a child process
|
93
89
|
def child(role)
|
94
90
|
@cached_children ||= {}
|
95
|
-
@cached_children[role.to_sym] ||=
|
96
|
-
|
97
|
-
|
91
|
+
@cached_children[role.to_sym] ||= self.children.where(:role => role).first || :nil
|
92
|
+
@cached_children[role.to_sym] == :nil ? nil : @cached_children[role.to_sym]
|
93
|
+
end
|
94
|
+
|
95
|
+
# Try to return a given otherwise revert to the parent
|
96
|
+
def try(role)
|
97
|
+
child(role) || self
|
98
98
|
end
|
99
99
|
|
100
100
|
# Add a child attachment
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Attach
|
2
|
+
class AttachmentDSL
|
3
|
+
|
4
|
+
attr_reader :processors
|
5
|
+
attr_reader :validators
|
6
|
+
|
7
|
+
def initialize(&block)
|
8
|
+
@processors = []
|
9
|
+
@validators = []
|
10
|
+
if block_given?
|
11
|
+
instance_eval(&block)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def processor(*processors, &block)
|
16
|
+
processors.each { |p| @processors << p }
|
17
|
+
@processors << block if block_given?
|
18
|
+
end
|
19
|
+
|
20
|
+
def validator(*validators, &block)
|
21
|
+
validators.each { |v| @validators << v }
|
22
|
+
@validators << block if block_given?
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -28,7 +28,17 @@ module Attach
|
|
28
28
|
# Return the URL that this attachment can be accessed at
|
29
29
|
#
|
30
30
|
def url(attachment)
|
31
|
-
"/attachment/#{attachment.token}/#{attachment.file_name}"
|
31
|
+
"#{Attach.asset_host}/attachment/#{attachment.token}/#{attachment.file_name}"
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# Return binaries for a set of files. They should be returned as a hash consisting
|
36
|
+
# of the attachment ID followed by the data
|
37
|
+
#
|
38
|
+
def read_multi(attachments)
|
39
|
+
attachments.compact.each_with_object({}) do |attachment, hash|
|
40
|
+
hash[attachment] = read(attachment)
|
41
|
+
end
|
32
42
|
end
|
33
43
|
|
34
44
|
end
|
@@ -22,6 +22,12 @@ module Attach
|
|
22
22
|
AttachmentBinary.where(:attachment_id => attachment.id).destroy_all
|
23
23
|
end
|
24
24
|
|
25
|
+
def read_multi(attachments)
|
26
|
+
AttachmentBinary.where(:attachment_id => attachments.map(&:id)).each_with_object({}) do |binary, hash|
|
27
|
+
hash[binary.attachment_id] = binary.data
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
25
31
|
end
|
26
32
|
end
|
27
33
|
end
|
data/lib/attach/file.rb
ADDED
data/lib/attach/middleware.rb
CHANGED
@@ -9,11 +9,11 @@ module Attach
|
|
9
9
|
|
10
10
|
def call(env)
|
11
11
|
if env['PATH_INFO'] =~ /\A\/attachment\/([a-f0-9\-]{36})\/(.*)/
|
12
|
-
if attachment = Attach::Attachment.find_by_token($1)
|
12
|
+
if attachment = Attach::Attachment.where(:serve => true).find_by_token($1)
|
13
13
|
[200, {
|
14
14
|
'Content-Length' => attachment.file_size.to_s,
|
15
15
|
'Content-Type' => attachment.file_type,
|
16
|
-
'Cache-Control' => "#{attachment.cache_type || 'private'}, immutable,
|
16
|
+
'Cache-Control' => "#{attachment.cache_type || 'private'}, immutable, max-age=#{attachment.cache_max_age || 30.days.to_i}",
|
17
17
|
'Content-Disposition' => "#{attachment.disposition || 'attachment'}; filename=\"#{attachment.file_name}\","
|
18
18
|
},
|
19
19
|
[attachment.binary]]
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'attach/attachment'
|
2
2
|
require 'attach/processor'
|
3
|
+
require 'attach/attachment_dsl'
|
3
4
|
|
4
5
|
module Attach
|
5
6
|
module ModelExtension
|
@@ -12,15 +13,7 @@ module Attach
|
|
12
13
|
end
|
13
14
|
|
14
15
|
if @pending_attachments
|
15
|
-
@pending_attachments.each
|
16
|
-
attachment = self.attachments.build(:uploaded_file => pa[:file], :role => pa[:role])
|
17
|
-
if pa[:options]
|
18
|
-
pa[:options].each do |key, value|
|
19
|
-
attachment.send("#{key}=", value)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
attachment.save!
|
23
|
-
end
|
16
|
+
@pending_attachments.values.each(&:save!)
|
24
17
|
@pending_attachments = nil
|
25
18
|
end
|
26
19
|
end
|
@@ -33,43 +26,77 @@ module Attach
|
|
33
26
|
if records.empty?
|
34
27
|
# Nothing to do
|
35
28
|
else
|
29
|
+
|
36
30
|
if options.first.is_a?(Hash)
|
37
31
|
options = options.first
|
32
|
+
binaries_to_include = options.delete(:_include_binaries) || {}
|
38
33
|
else
|
39
|
-
|
40
|
-
|
34
|
+
binaries_to_include = {}
|
35
|
+
options = options.each_with_object({}) do |opt, hash|
|
36
|
+
if opt.is_a?(Symbol) || opt.is_a?(String)
|
37
|
+
hash[opt.to_sym] = []
|
38
|
+
elsif opt.is_a?(Hash)
|
39
|
+
opt.each do |key, value|
|
40
|
+
if key == :_include_binaries
|
41
|
+
binaries_to_include = value
|
42
|
+
else
|
43
|
+
hash[key.to_sym] = value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
41
47
|
end
|
48
|
+
|
42
49
|
end
|
43
50
|
|
44
51
|
options.keys.each do |key|
|
45
52
|
if options[key].is_a?(Symbol)
|
46
53
|
options[key] = [options[key]]
|
54
|
+
elsif options[key] == true
|
55
|
+
options[key] = []
|
47
56
|
end
|
48
57
|
end
|
49
58
|
|
59
|
+
attachments_for_binary_preload = []
|
50
60
|
root_attachments = {}
|
51
61
|
Attachment.where(:owner_id => records.map(&:id), :owner_type => records.first.class.to_s, :role => options.keys).each do |attachment|
|
52
62
|
root_attachments[[attachment.owner_id, attachment.role]] = attachment
|
63
|
+
if binaries_to_include[attachment.role.to_sym] && binaries_to_include[attachment.role.to_sym].include?(:_self)
|
64
|
+
attachments_for_binary_preload << attachment
|
65
|
+
end
|
53
66
|
end
|
54
67
|
|
55
|
-
child_attachments = {}
|
56
68
|
child_roles = options.values.flatten
|
57
|
-
|
58
|
-
child_attachments
|
69
|
+
unless child_roles.empty?
|
70
|
+
child_attachments = {}
|
71
|
+
Attachment.where(:parent_id => root_attachments.values.map(&:id), :role => child_roles).each do |attachment|
|
72
|
+
child_attachments[[attachment.parent_id, attachment.role]] = attachment
|
73
|
+
end
|
74
|
+
|
75
|
+
root_attachments.values.each do |attachment|
|
76
|
+
options[attachment.role.to_sym].each do |role|
|
77
|
+
child_attachment = child_attachments[[attachment.id, role.to_s]]
|
78
|
+
|
79
|
+
if child_attachment && binaries_to_include[attachment.role.to_sym] && binaries_to_include[attachment.role.to_sym].include?(role)
|
80
|
+
attachments_for_binary_preload << child_attachment
|
81
|
+
end
|
82
|
+
|
83
|
+
attachment.instance_variable_set("@cached_children", {}) if attachment.instance_variable_get("@cached_children").nil?
|
84
|
+
attachment.instance_variable_get("@cached_children")[role.to_sym] = child_attachments[[attachment.id, role.to_s]] || :nil
|
85
|
+
end
|
86
|
+
end
|
59
87
|
end
|
60
88
|
|
61
|
-
|
62
|
-
|
63
|
-
attachment.instance_variable_set("@
|
64
|
-
attachment.instance_variable_get("@cached_children")[role.to_sym] = attachment
|
89
|
+
if binaries = Attach.backend.read_multi(attachments_for_binary_preload)
|
90
|
+
attachments_for_binary_preload.each do |attachment|
|
91
|
+
attachment.instance_variable_set("@binary", binaries[attachment.id] || :nil)
|
65
92
|
end
|
93
|
+
else
|
94
|
+
# Preloading binaries isn't supported by the backend
|
66
95
|
end
|
67
96
|
|
68
97
|
records.each do |record|
|
69
98
|
options.keys.each do |role|
|
70
|
-
|
71
|
-
record.instance_variable_set("@#{role}", a)
|
72
|
-
end
|
99
|
+
record.instance_variable_set("@#{role}", root_attachments[[record.id, role.to_s]] || :nil)
|
73
100
|
end
|
74
101
|
end
|
75
102
|
end
|
@@ -81,29 +108,63 @@ module Attach
|
|
81
108
|
has_many :attachments, :as => :owner, :dependent => :destroy, :class_name => 'Attach::Attachment'
|
82
109
|
end
|
83
110
|
|
84
|
-
|
85
|
-
|
111
|
+
dsl = AttachmentDSL.new(&block)
|
112
|
+
|
113
|
+
dsl.processors.each do |processor|
|
114
|
+
Processor.register(self, name, &processor)
|
86
115
|
end
|
87
116
|
|
88
|
-
|
89
|
-
|
90
|
-
attachment =
|
91
|
-
|
117
|
+
if dsl.validators.size > 0
|
118
|
+
validate do
|
119
|
+
attachment = @pending_attachments && @pending_attachments[name] ? @pending_attachments[name] : send(name)
|
120
|
+
file_errors = []
|
121
|
+
dsl.validators.each do |validator|
|
122
|
+
validator.call(attachment, file_errors)
|
123
|
+
end
|
124
|
+
file_errors.each { |e| errors.add("#{name}_file", e) }
|
92
125
|
end
|
93
126
|
end
|
94
127
|
|
95
|
-
define_method
|
96
|
-
instance_variable_get("@#{name}
|
128
|
+
define_method name do
|
129
|
+
var = instance_variable_get("@#{name}")
|
130
|
+
if var
|
131
|
+
var == :nil ? nil : var
|
132
|
+
else
|
133
|
+
if attachment = self.attachments.where(:role => name, :parent_id => nil).first
|
134
|
+
instance_variable_set("@#{name}", attachment)
|
135
|
+
else
|
136
|
+
instance_variable_set("@#{name}", :nil)
|
137
|
+
nil
|
138
|
+
end
|
139
|
+
end
|
97
140
|
end
|
98
141
|
|
99
|
-
define_method "#{name}
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
142
|
+
define_method "#{name}=" do |file|
|
143
|
+
if file.is_a?(Attach::Attachment)
|
144
|
+
attachment = file
|
145
|
+
elsif file
|
146
|
+
attachment = Attachment.new({:owner => self, :role => name}.merge(options))
|
147
|
+
case file
|
148
|
+
when ActionDispatch::Http::UploadedFile
|
149
|
+
attachment.binary = file.tempfile.read
|
150
|
+
attachment.file_name = file.original_filename
|
151
|
+
attachment.file_type = file.content_type
|
152
|
+
when Attach::File
|
153
|
+
attachment.binary = file.data
|
154
|
+
attachment.file_name = file.name
|
155
|
+
attachment.file_type = file.type
|
156
|
+
else
|
157
|
+
attachment.binary = file
|
158
|
+
attachment.file_name = "untitled"
|
159
|
+
attachment.file_type = "application/octet-stream"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
if attachment
|
164
|
+
@pending_attachments ||= {}
|
165
|
+
@pending_attachments[name] = attachment
|
106
166
|
end
|
167
|
+
instance_variable_set("@#{name}", attachment)
|
107
168
|
end
|
108
169
|
|
109
170
|
define_method "#{name}_delete" do
|
data/lib/attach/processor.rb
CHANGED
data/lib/attach/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attach
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Cooke
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-03-
|
11
|
+
date: 2017-03-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: records_manipulator
|
@@ -40,9 +40,11 @@ files:
|
|
40
40
|
- lib/attach.rb
|
41
41
|
- lib/attach/attachment.rb
|
42
42
|
- lib/attach/attachment_binary.rb
|
43
|
+
- lib/attach/attachment_dsl.rb
|
43
44
|
- lib/attach/backends/abstract.rb
|
44
45
|
- lib/attach/backends/database.rb
|
45
46
|
- lib/attach/backends/file_system.rb
|
47
|
+
- lib/attach/file.rb
|
46
48
|
- lib/attach/middleware.rb
|
47
49
|
- lib/attach/model_extension.rb
|
48
50
|
- lib/attach/processor.rb
|
@@ -73,3 +75,4 @@ signing_key:
|
|
73
75
|
specification_version: 4
|
74
76
|
summary: Attach documents & files to Active Record models
|
75
77
|
test_files: []
|
78
|
+
has_rdoc:
|