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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bba016e24c62143435904ab7adec8d742dfc2cff
4
- data.tar.gz: 7fd96092ed4196d2eb765788adf376bbb8caec46
3
+ metadata.gz: 97d4588d2fde59f28aa8079b2ebc8d423c200e3d
4
+ data.tar.gz: 98b5176bb728e78f37b0193d0934916b98162e63
5
5
  SHA512:
6
- metadata.gz: 1728d88028c90ca2701943199a9fecfe5cbe4b6f3358a3533f617aa279942e05974f94d3bb7089e31e99af1082781a2f9979cb2857d617c07d6399f590ef2e09
7
- data.tar.gz: abb0b019475de3fb68a433aa99a80facdd95c86701e76ba5fea90c7b097e0bb507337a1404f67571f2aceb9aae8cf1f2ef0248fedc87ca37764e007f7d5f33fa
6
+ metadata.gz: b1fb19b9cc73ac94e525b962c7212bcbff13bc7cad628c184235d1992f00e16c9963e0377839ebfb0d38f744832a1c59689a29ad9de7c89cd65022596798aaef
7
+ data.tar.gz: 9bc6875dfa3e5d8017872ec0924b588791f70194fa567b12ca66584cc561026e4d1c025d532d1e4b9149780cd23c5817537cc573b6ce8ee4bec2389b0b16b724
@@ -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)
@@ -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
- # Copy values from the `uploaded_file` and set them as the appropriate
33
- # fields on this model
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] ||= begin
96
- self.children.where(:role => role).first
97
- end
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
@@ -0,0 +1,15 @@
1
+ module Attach
2
+ class File
3
+
4
+ attr_accessor :data
5
+ attr_accessor :name
6
+ attr_accessor :type
7
+
8
+ def initialize(data, name = "untitled", type = "application/octet-stream")
9
+ @data = data
10
+ @name = name
11
+ @type = type
12
+ end
13
+
14
+ end
15
+ end
@@ -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, maxage=#{attachment.cache_max_age || 30.days.to_i}",
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 do |pa|
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
- options = options.each_with_object({}) do |role, hash|
40
- hash[role.to_sym] = []
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
- Attachment.where(:parent_id => root_attachments.values.map(&:id), :role => child_roles).each do |attachment|
58
- child_attachments[[attachment.id, attachment.role]] = attachment
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
- root_attachments.values.each do |attachment|
62
- options[attachment.role.to_sym].each do |role|
63
- attachment.instance_variable_set("@cached_children", {}) if attachment.instance_variable_get("@cached_children").nil?
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
- if a = root_attachments[[record.id, role.to_s]]
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
- if block_given?
85
- Processor.register(self, name, &block)
111
+ dsl = AttachmentDSL.new(&block)
112
+
113
+ dsl.processors.each do |processor|
114
+ Processor.register(self, name, &processor)
86
115
  end
87
116
 
88
- define_method name do
89
- instance_variable_get("@#{name}") || begin
90
- attachment = self.attachments.where(:role => name, :parent_id => nil).first
91
- instance_variable_set("@#{name}", attachment)
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 "#{name}_file" do
96
- instance_variable_get("@#{name}_file")
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}_file=" do |file|
100
- instance_variable_set("@#{name}_file", file)
101
- if file.is_a?(ActionDispatch::Http::UploadedFile)
102
- @pending_attachments ||= []
103
- @pending_attachments << {:role => name, :file => file, :options => options}
104
- else
105
- nil
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
@@ -25,7 +25,8 @@ module Attach
25
25
 
26
26
  def process
27
27
  call_processors(@attachment)
28
- @attachment.update_column(:processed, true)
28
+ @attachment.processed = true
29
+ @attachment.save(:validate => false)
29
30
  end
30
31
 
31
32
  def queue_or_process
@@ -1,3 +1,3 @@
1
1
  module Attach
2
- VERSION = '1.0.0'
2
+ VERSION = '1.0.1'
3
3
  end
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.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-10 00:00:00.000000000 Z
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: