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 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: