adrift 0.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.
- data/.gitignore +6 -0
- data/.rspec +1 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.rdoc +10 -0
- data/Rakefile +22 -0
- data/adrift.gemspec +38 -0
- data/autotest/discover.rb +1 -0
- data/features/active_record_integration.feature +42 -0
- data/features/data_mapper_integration.feature +42 -0
- data/features/step_definitions/model_steps.rb +54 -0
- data/features/support/env.rb +55 -0
- data/features/support/world.rb +58 -0
- data/lib/adrift.rb +10 -0
- data/lib/adrift/attachment.rb +246 -0
- data/lib/adrift/file_to_attach.rb +121 -0
- data/lib/adrift/integration.rb +39 -0
- data/lib/adrift/integration/active_record.rb +29 -0
- data/lib/adrift/integration/base.rb +87 -0
- data/lib/adrift/integration/data_mapper.rb +29 -0
- data/lib/adrift/pattern.rb +219 -0
- data/lib/adrift/processor.rb +100 -0
- data/lib/adrift/railtie.rb +12 -0
- data/lib/adrift/storage.rb +82 -0
- data/lib/adrift/version.rb +3 -0
- data/spec/adrift/attachment_spec.rb +488 -0
- data/spec/adrift/file_to_attach_spec.rb +78 -0
- data/spec/adrift/integration/active_record_spec.rb +21 -0
- data/spec/adrift/integration/base_spec.rb +7 -0
- data/spec/adrift/integration/data_mapper_spec.rb +21 -0
- data/spec/adrift/pattern_spec.rb +98 -0
- data/spec/adrift/processor_spec.rb +61 -0
- data/spec/adrift/storage_spec.rb +181 -0
- data/spec/fixtures/me.png +0 -0
- data/spec/fixtures/me_no_colors.png +0 -0
- data/spec/shared_examples/integration/base.rb +128 -0
- data/spec/spec_helper.rb +47 -0
- metadata +277 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Gabriel Andretta
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
= Adrift
|
2
|
+
|
3
|
+
Adrift is a DIY library to ease attaching files to a model.
|
4
|
+
|
5
|
+
It currently works only with ActiveRecord and DataMapper within Rails
|
6
|
+
or Sinatra, but it should be pretty adaptable to another environment.
|
7
|
+
|
8
|
+
== License
|
9
|
+
|
10
|
+
Adrift is released under MIT license. See LICENSE.
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
RSpec::Core::RakeTask.new
|
6
|
+
|
7
|
+
require 'cucumber'
|
8
|
+
require 'cucumber/rake/task'
|
9
|
+
Cucumber::Rake::Task.new(:features)
|
10
|
+
|
11
|
+
require 'rdoc/task'
|
12
|
+
RDoc::Task.new do |rdoc|
|
13
|
+
rdoc.rdoc_dir = 'doc'
|
14
|
+
rdoc.title = 'Adrift'
|
15
|
+
rdoc.main = 'README.rdoc'
|
16
|
+
|
17
|
+
rdoc.rdoc_files.include('README.rdoc')
|
18
|
+
rdoc.rdoc_files.include('LICENSE')
|
19
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
20
|
+
end
|
21
|
+
|
22
|
+
task :default => :spec
|
data/adrift.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "adrift/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "adrift"
|
7
|
+
s.version = Adrift::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Gabriel Andretta"]
|
10
|
+
s.email = ["ohhgabriel@gmail.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = "Simplistic attachment management"
|
13
|
+
s.description = "Simplistic attachment management"
|
14
|
+
|
15
|
+
s.rubyforge_project = "adrift"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
|
19
|
+
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.has_rdoc = true
|
23
|
+
|
24
|
+
s.add_dependency 'activesupport', '~>3.0'
|
25
|
+
s.add_dependency 'i18n'
|
26
|
+
|
27
|
+
s.add_development_dependency 'rspec', '~>2.4'
|
28
|
+
s.add_development_dependency 'cucumber', '~>0.10'
|
29
|
+
s.add_development_dependency 'activerecord', '~>3.0'
|
30
|
+
s.add_development_dependency 'dm-core', '~>1.0'
|
31
|
+
s.add_development_dependency 'dm-migrations'
|
32
|
+
s.add_development_dependency 'dm-validations'
|
33
|
+
s.add_development_dependency 'dm-sqlite-adapter'
|
34
|
+
s.add_development_dependency 'sqlite3'
|
35
|
+
s.add_development_dependency 'rake'
|
36
|
+
s.add_development_dependency 'ZenTest'
|
37
|
+
s.add_development_dependency 'rdoc', '~>3.5'
|
38
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Autotest.add_discovery { "rspec2" }
|
@@ -0,0 +1,42 @@
|
|
1
|
+
Feature: ActiveRecord integration
|
2
|
+
|
3
|
+
In order to handle file attachments easily
|
4
|
+
As a Ruby developer using ActiveRecord
|
5
|
+
I want to let Adrift do the dirty work
|
6
|
+
|
7
|
+
Scenario: A file is attached
|
8
|
+
Given I instantiate an active record model
|
9
|
+
When I attach a file to it
|
10
|
+
And I save it
|
11
|
+
Then the file should be stored
|
12
|
+
|
13
|
+
Scenario: A file is attached to an invalid model
|
14
|
+
Given I instantiate an invalid active record model
|
15
|
+
When I attach a file to it
|
16
|
+
And I try to save it
|
17
|
+
Then it should not be saved
|
18
|
+
And the file should not be stored
|
19
|
+
|
20
|
+
Scenario: Two files are attached
|
21
|
+
Given I instantiate an active record model
|
22
|
+
When I attach a file to it
|
23
|
+
And I save it
|
24
|
+
And I attach another file to it
|
25
|
+
And I save it again
|
26
|
+
Then the first file should not still be stored
|
27
|
+
And the second file should be stored
|
28
|
+
|
29
|
+
Scenario: A file is detached
|
30
|
+
Given I instantiate an active record model
|
31
|
+
When I attach a file to it
|
32
|
+
And I save it
|
33
|
+
And I detach the file from it
|
34
|
+
And I save it
|
35
|
+
Then the file should not still be stored
|
36
|
+
|
37
|
+
Scenario: A model with an attached file is destroyed
|
38
|
+
Given I instantiate an active record model
|
39
|
+
When I attach a file to it
|
40
|
+
And I save it
|
41
|
+
And I destroy it
|
42
|
+
Then the file should not still be stored
|
@@ -0,0 +1,42 @@
|
|
1
|
+
Feature: DataMapper integration
|
2
|
+
|
3
|
+
In order to handle file attachments easily
|
4
|
+
As a Ruby developer using DataMapper
|
5
|
+
I want to let Adrift do the dirty work
|
6
|
+
|
7
|
+
Scenario: A file is attached
|
8
|
+
Given I instantiate a data mapper model
|
9
|
+
When I attach a file to it
|
10
|
+
And I save it
|
11
|
+
Then the file should be stored
|
12
|
+
|
13
|
+
Scenario: A file is attached to an invalid model
|
14
|
+
Given I instantiate an invalid data mapper model
|
15
|
+
When I attach a file to it
|
16
|
+
And I try to save it
|
17
|
+
Then it should not be saved
|
18
|
+
And the file should not be stored
|
19
|
+
|
20
|
+
Scenario: Two files are attached
|
21
|
+
Given I instantiate a data mapper model
|
22
|
+
When I attach a file to it
|
23
|
+
And I save it
|
24
|
+
And I attach another file to it
|
25
|
+
And I save it again
|
26
|
+
Then the first file should not still be stored
|
27
|
+
And the second file should be stored
|
28
|
+
|
29
|
+
Scenario: A file is detached
|
30
|
+
Given I instantiate a data mapper model
|
31
|
+
When I attach a file to it
|
32
|
+
And I save it
|
33
|
+
And I detach the file from it
|
34
|
+
And I save it
|
35
|
+
Then the file should not still be stored
|
36
|
+
|
37
|
+
Scenario: A model with an attached file is destroyed
|
38
|
+
Given I instantiate a data mapper model
|
39
|
+
When I attach a file to it
|
40
|
+
And I save it
|
41
|
+
And I destroy it
|
42
|
+
Then the file should not still be stored
|
@@ -0,0 +1,54 @@
|
|
1
|
+
Given /^I instantiate an active record model$/ do
|
2
|
+
instantiate :active_record
|
3
|
+
end
|
4
|
+
|
5
|
+
Given /^I instantiate an invalid active record model$/ do
|
6
|
+
instantiate :active_record, :valid => false
|
7
|
+
end
|
8
|
+
|
9
|
+
Given /^I instantiate a data mapper model$/ do
|
10
|
+
instantiate :data_mapper
|
11
|
+
end
|
12
|
+
|
13
|
+
Given /^I instantiate an invalid data mapper model$/ do
|
14
|
+
instantiate :data_mapper, :valid => false
|
15
|
+
end
|
16
|
+
|
17
|
+
When /^I attach a(?:nother)? file to it$/ do
|
18
|
+
attach(attached? ? other_original_file : original_file)
|
19
|
+
end
|
20
|
+
|
21
|
+
When /^I(?: try to)? save it(?: again)?$/ do
|
22
|
+
instance.save
|
23
|
+
end
|
24
|
+
|
25
|
+
When /^I destroy it$/ do
|
26
|
+
instance.destroy
|
27
|
+
end
|
28
|
+
|
29
|
+
When /^I detach the file from it$/ do
|
30
|
+
detach
|
31
|
+
end
|
32
|
+
|
33
|
+
Then /^it should not be saved$/ do
|
34
|
+
if instance.respond_to?(:persisted?)
|
35
|
+
instance.should_not be_persisted
|
36
|
+
else
|
37
|
+
instance.should_not be_saved
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
Then /^the(?: second)? file should be stored$/ do
|
42
|
+
File.exist?(last_attachment).should be_true
|
43
|
+
read_file(last_attachment).should == read_file(last_file)
|
44
|
+
end
|
45
|
+
|
46
|
+
Then /^the file should not(?: still)? be stored$/ do
|
47
|
+
last_attachment.should_not be_nil
|
48
|
+
File.exist?(last_attachment).should be_false
|
49
|
+
end
|
50
|
+
|
51
|
+
Then /^the first file should not still be stored$/ do
|
52
|
+
first_attachment.should_not be_nil
|
53
|
+
File.exist?(first_attachment).should be_false
|
54
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
$:.unshift File.expand_path('../../../lib', __FILE__)
|
2
|
+
require 'cucumber'
|
3
|
+
require 'adrift'
|
4
|
+
require 'adrift/integration'
|
5
|
+
|
6
|
+
require 'active_record'
|
7
|
+
Adrift::Integration::ActiveRecord.install
|
8
|
+
|
9
|
+
ActiveRecord::Base.establish_connection(
|
10
|
+
:adapter => 'sqlite3',
|
11
|
+
:database => '/tmp/adrift-activerecord.sqlite3'
|
12
|
+
)
|
13
|
+
|
14
|
+
ActiveRecord::Migration.verbose = false
|
15
|
+
|
16
|
+
ActiveRecord::Schema.define(:version => 1) do
|
17
|
+
create_table 'ar_users', :force => true do |t|
|
18
|
+
t.string 'name'
|
19
|
+
t.string 'avatar_filename'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class ARUser < ActiveRecord::Base
|
24
|
+
validates :name, :presence => true
|
25
|
+
attachment :avatar
|
26
|
+
end
|
27
|
+
|
28
|
+
require 'dm-core'
|
29
|
+
require 'dm-validations'
|
30
|
+
require 'dm-migrations'
|
31
|
+
Adrift::Integration::DataMapper.install
|
32
|
+
|
33
|
+
DataMapper.setup(:default, 'sqlite::memory:')
|
34
|
+
|
35
|
+
class DMUser
|
36
|
+
include DataMapper::Resource
|
37
|
+
|
38
|
+
property :id, Serial
|
39
|
+
property :name, String
|
40
|
+
property :avatar_filename, String
|
41
|
+
|
42
|
+
validates_presence_of :name
|
43
|
+
|
44
|
+
attachment :avatar
|
45
|
+
end
|
46
|
+
|
47
|
+
DataMapper.finalize
|
48
|
+
DataMapper.auto_migrate!
|
49
|
+
|
50
|
+
Before do
|
51
|
+
ARUser.delete_all
|
52
|
+
DMUser.destroy
|
53
|
+
end
|
54
|
+
|
55
|
+
After { system 'rm -rf public' }
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Adrift
|
2
|
+
module Cucumber
|
3
|
+
module Attachment
|
4
|
+
def read_file(path)
|
5
|
+
File.open(path, 'rb') { |io| io.read }
|
6
|
+
end
|
7
|
+
|
8
|
+
def original_file
|
9
|
+
'spec/fixtures/me.png'
|
10
|
+
end
|
11
|
+
|
12
|
+
def other_original_file
|
13
|
+
'spec/fixtures/me_no_colors.png'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module Model
|
18
|
+
attr_accessor :instance, :last_file, :last_attachment, :first_file,
|
19
|
+
:first_attachment
|
20
|
+
|
21
|
+
def instantiate(orm, opts={ :valid => true })
|
22
|
+
@last_id ||= 0
|
23
|
+
self.instance = class_for(orm).new.tap do |user|
|
24
|
+
user.id = @last_id += 1
|
25
|
+
user.name = 'ohhgabriel' if opts[:valid]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def attached?
|
30
|
+
!last_file.nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
def attach(path)
|
34
|
+
instance.avatar = File.new(path)
|
35
|
+
if first_file.nil?
|
36
|
+
self.first_file = last_file
|
37
|
+
self.first_attachment = last_attachment
|
38
|
+
end
|
39
|
+
self.last_file = path
|
40
|
+
self.last_attachment = instance.avatar.path
|
41
|
+
end
|
42
|
+
|
43
|
+
def detach
|
44
|
+
instance.avatar.destroy
|
45
|
+
end
|
46
|
+
|
47
|
+
def class_for(orm)
|
48
|
+
case orm
|
49
|
+
when :active_record then ARUser
|
50
|
+
when :data_mapper then DMUser
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
World(Adrift::Cucumber::Attachment)
|
58
|
+
World(Adrift::Cucumber::Model)
|
data/lib/adrift.rb
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
module Adrift
|
2
|
+
# Handles attaching files to a model, allowing to automatically
|
3
|
+
# create different stlyes (versions) of them.
|
4
|
+
#
|
5
|
+
# Actually, this class's responsibility consist in just directing
|
6
|
+
# this process: it relies on a #storage object, for saving and
|
7
|
+
# removing the attached files, and on a #processor object, for the
|
8
|
+
# task of generating the different versions of a file from the given
|
9
|
+
# style definitions.
|
10
|
+
#
|
11
|
+
# Also, it provides a naive pattern mechanism to express the
|
12
|
+
# attachment's #path and #url.
|
13
|
+
class Attachment
|
14
|
+
attr_accessor :default_style, :styles, :storage, :processor, :pattern_class
|
15
|
+
attr_writer :default_url, :url, :path
|
16
|
+
attr_reader :name, :model
|
17
|
+
|
18
|
+
# Allows to change the options used for every new attachment. For
|
19
|
+
# instance, to change the +:default_style+ and +:path+ options:
|
20
|
+
#
|
21
|
+
# Adrift::Attachment.config do
|
22
|
+
# default_style :default
|
23
|
+
# path '/custom/storage/path/:url'
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# See ::default_options for a list of the supported options.
|
27
|
+
def self.config(&block)
|
28
|
+
config = BasicObject.new
|
29
|
+
def config.method_missing(m, *args)
|
30
|
+
options = Attachment.default_options
|
31
|
+
options[m] = args.first if options.has_key?(m)
|
32
|
+
end
|
33
|
+
config.instance_eval(&block)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Default options for every new Attachment. These are:
|
37
|
+
#
|
38
|
+
# [+:default_style+]
|
39
|
+
# Style assumed by #url and #path when no one has been provided.
|
40
|
+
#
|
41
|
+
# [+:styles+]
|
42
|
+
# Hash with the style definitions, they keys are the style
|
43
|
+
# names, and the values whatever makes sense to the processor to
|
44
|
+
# generate the alternate versions of the attached file. By
|
45
|
+
# default, they indicate the thumbnails dimmensions, for
|
46
|
+
# instance:
|
47
|
+
#
|
48
|
+
# styles: { small: '50x50', medium: '100x100' }
|
49
|
+
#
|
50
|
+
# See Processor::Thumbnail for the details.
|
51
|
+
#
|
52
|
+
# [+:default_url+]
|
53
|
+
# String pattern used to build the returned value of #url when
|
54
|
+
# the attachment is empty.
|
55
|
+
#
|
56
|
+
# [+:url+]
|
57
|
+
# String pattern used to build the returned value of #url when
|
58
|
+
# the attachment is not empty.
|
59
|
+
#
|
60
|
+
# [+:path+]
|
61
|
+
# String pattern used to build the path where the attachment
|
62
|
+
# will be stored (and will be returned by #path).
|
63
|
+
#
|
64
|
+
# *Note*: when having an attachment with more than one style,
|
65
|
+
# the path must be unique for each one, otherwise the stored
|
66
|
+
# files will be overwritten. In the most common case, what this
|
67
|
+
# means is just that the path option must have a +:style+ tag.
|
68
|
+
#
|
69
|
+
# [+:storage+]
|
70
|
+
# Object delegated with the task of saving and removing files.
|
71
|
+
# See Storage for details and Storage::Filesystem for an
|
72
|
+
# implementation.
|
73
|
+
#
|
74
|
+
# [+:processor+]
|
75
|
+
# Object delegated with the task of generating the alternate
|
76
|
+
# versions of the attached file from the :styles. See Processor
|
77
|
+
# for details and Processor::Thumbnail for an implementation.
|
78
|
+
#
|
79
|
+
# [+:pattern_class+]
|
80
|
+
# The class used to build the #url and #path of the Attachment
|
81
|
+
# from the string patterns provided.
|
82
|
+
#
|
83
|
+
# See the source of this method to know the values of this
|
84
|
+
# options. Note that these values can be changed for every new
|
85
|
+
# attachment with ::config, or in a per-attachment basis, when
|
86
|
+
# they are constructed with ::new, or after, just calling the
|
87
|
+
# attachment's writer method named after the option.
|
88
|
+
def self.default_options
|
89
|
+
@default_options ||= {
|
90
|
+
:default_style => :original,
|
91
|
+
:styles => {},
|
92
|
+
:default_url => '/:attachment/:style/missing.png',
|
93
|
+
:url => '/system/:attachment/:id/:style/:filename',
|
94
|
+
:path => ':root/public:url',
|
95
|
+
:storage => Proc.new { Storage::Filesystem.new },
|
96
|
+
:processor => Proc.new { Processor::Thumbnail.new },
|
97
|
+
:pattern_class => Pattern
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
# Restores the attachment's options to their default values. See
|
102
|
+
# the source of ::default_options to know what these default
|
103
|
+
# values are.
|
104
|
+
def self.reset_default_options
|
105
|
+
@default_options = nil
|
106
|
+
end
|
107
|
+
|
108
|
+
# Creates a new Attachment object. +name+ is the name of the
|
109
|
+
# Attachment, +model+ is the model object it's attached to, and
|
110
|
+
# +options+ is a Hash that lets customize the Attachment's
|
111
|
+
# behaviour.
|
112
|
+
#
|
113
|
+
# The +model+ object must allow reading and writing to an
|
114
|
+
# attribute called after the Attachment's +name+ which stores the
|
115
|
+
# attached file's name. For instance, for an attachment named
|
116
|
+
# +avatar+, the +model+ needs to respond to the methods
|
117
|
+
# +avatar_filename+ and +avatar_filename=+.
|
118
|
+
#
|
119
|
+
# See ::default_options for a list of the supported +options+.
|
120
|
+
# The options passed here will overwrite the default ones.
|
121
|
+
def initialize(name, model, options={})
|
122
|
+
self.class.default_options.merge(options).each do |name, value|
|
123
|
+
writer_name = "#{name}="
|
124
|
+
if respond_to?(writer_name)
|
125
|
+
send writer_name, value.is_a?(Proc) ? value.call : value
|
126
|
+
end
|
127
|
+
end
|
128
|
+
@name, @model = name, model
|
129
|
+
end
|
130
|
+
|
131
|
+
# Indicates whether or not there are changes that need to be
|
132
|
+
# saved, that is, files that need to be processed and stored
|
133
|
+
# and/or removed.
|
134
|
+
def dirty?
|
135
|
+
!@file_to_attach.nil? || storage.dirty?
|
136
|
+
end
|
137
|
+
|
138
|
+
# Returns the attachment's url for the given +style+. If no
|
139
|
+
# +style+ is given it assumes the +:default_style+ option. Also,
|
140
|
+
# it uses the +:url+ or the +:default_url+ option, depending
|
141
|
+
# whether or not the attachment is empty.
|
142
|
+
def url(style=default_style)
|
143
|
+
specialize(empty? ? @default_url : @url, style)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns the attachment's path for the given +style+. If no
|
147
|
+
# +style+ is given it assumes +:default_style+ option. When the
|
148
|
+
# attachment is empty it returns +nil+.
|
149
|
+
def path(style=default_style)
|
150
|
+
specialize(@path, style) unless empty?
|
151
|
+
end
|
152
|
+
|
153
|
+
# Makes +file_to_attach+ the new attached file, but it won't be
|
154
|
+
# stored nor processed until the attachment receives #save. It
|
155
|
+
# also updates the model's attachment file name attribute.
|
156
|
+
#
|
157
|
+
# See FileToAttach::Adapters for the expected interface of
|
158
|
+
# +file_to_attach+.
|
159
|
+
def assign(file_to_attach)
|
160
|
+
enqueue_files_for_removal
|
161
|
+
model_send(
|
162
|
+
:filename=,
|
163
|
+
file_to_attach.original_filename.to_s.tr('^a-zA-Z0-9.', '_')
|
164
|
+
)
|
165
|
+
@file_to_attach = file_to_attach
|
166
|
+
end
|
167
|
+
|
168
|
+
# Throws away the current attached file, but it won't actually be
|
169
|
+
# removed until the attachment receives #save. It also sets the
|
170
|
+
# model's attachment file name attribute to +nil+.
|
171
|
+
def clear
|
172
|
+
enqueue_files_for_removal
|
173
|
+
@file_to_attach = nil
|
174
|
+
model_send(:filename=, nil)
|
175
|
+
end
|
176
|
+
|
177
|
+
# When there is a new attached file will store and process it, and
|
178
|
+
# if there was a previous attached file it will also remove it.
|
179
|
+
# On the other hand it will remove the current attached file if it
|
180
|
+
# was thrown away (in other words, the attachment has received
|
181
|
+
# #clear).
|
182
|
+
#
|
183
|
+
# Generally, this will get called when the model is saved.
|
184
|
+
def save
|
185
|
+
unless @file_to_attach.nil?
|
186
|
+
processor.process(@file_to_attach.path, styles)
|
187
|
+
enqueue_files_for_storage
|
188
|
+
end
|
189
|
+
storage.flush
|
190
|
+
@file_to_attach = nil
|
191
|
+
end
|
192
|
+
|
193
|
+
# Removes the current attached file, setting the model's
|
194
|
+
# attachment file name attribute to +nil+.
|
195
|
+
def destroy
|
196
|
+
clear
|
197
|
+
save
|
198
|
+
end
|
199
|
+
|
200
|
+
# Indicates whether or not there is a file attached. Note that a
|
201
|
+
# file could be attached without being stored or processed.
|
202
|
+
def empty?
|
203
|
+
filename.nil?
|
204
|
+
end
|
205
|
+
|
206
|
+
# Returns the attachment's file name.
|
207
|
+
def filename
|
208
|
+
model_send(:filename)
|
209
|
+
end
|
210
|
+
|
211
|
+
private
|
212
|
+
|
213
|
+
# Sends the message +message_without_prefix+ to the model,
|
214
|
+
# prefixed with the attachment's name, and passing the given
|
215
|
+
# +args+.
|
216
|
+
def model_send(message_without_prefix, *args)
|
217
|
+
model.public_send("#{name}_#{message_without_prefix}", *args)
|
218
|
+
end
|
219
|
+
|
220
|
+
# Specializes the string pattern +str+, for the attachment which
|
221
|
+
# receives this message and the given +style+.
|
222
|
+
def specialize(str, style)
|
223
|
+
pattern_class.new(str).specialize(:attachment => self, :style => style)
|
224
|
+
end
|
225
|
+
|
226
|
+
# Adds all the current attached files to the storage's removal
|
227
|
+
# queue, unless they don't exist or are already there.
|
228
|
+
def enqueue_files_for_removal
|
229
|
+
return if empty? || dirty?
|
230
|
+
[:original, *styles.keys].uniq.each { |style| storage.remove path(style) }
|
231
|
+
end
|
232
|
+
|
233
|
+
# Adds all the current attached files to the storage's queue.
|
234
|
+
def enqueue_files_for_storage
|
235
|
+
files_for_storage.each { |style, file| storage.store(file, path(style)) }
|
236
|
+
end
|
237
|
+
|
238
|
+
# Returns a hash containing the files that need to be stored as
|
239
|
+
# values and their styles as keys.
|
240
|
+
def files_for_storage
|
241
|
+
processor.processed_files.dup.tap do |files|
|
242
|
+
files[:original] ||= @file_to_attach.path
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|