attach 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|