versions 0.1.0 → 0.2.0
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 +2 -1
- data/History.txt +6 -1
- data/README.rdoc +41 -7
- data/Rakefile +1 -0
- data/lib/versions.rb +3 -2
- data/lib/versions/after_commit.rb +69 -0
- data/lib/versions/attachment.rb +104 -0
- data/lib/versions/auto.rb +3 -2
- data/lib/versions/multi.rb +44 -16
- data/lib/versions/shared_attachment.rb +62 -2
- data/lib/versions/version.rb +1 -1
- data/test/fixtures.rb +8 -4
- data/test/fixtures/files/bird.jpg +0 -0
- data/test/fixtures/files/lake.jpg +0 -0
- data/test/helper.rb +35 -3
- data/test/unit/after_commit_test.rb +107 -0
- data/test/unit/attachment_test.rb +258 -220
- data/test/unit/auto_test.rb +20 -3
- data/test/unit/multi_test.rb +58 -14
- data/versions.gemspec +11 -5
- metadata +18 -5
- data/lib/versions/shared_attachment/attachment.rb +0 -66
- data/lib/versions/shared_attachment/owner.rb +0 -77
- data/lib/versions/transparent.rb +0 -98
data/lib/versions/version.rb
CHANGED
data/test/fixtures.rb
CHANGED
@@ -31,7 +31,8 @@ begin
|
|
31
31
|
t.string 'properties'
|
32
32
|
t.integer 'attachment_id'
|
33
33
|
t.integer 'number'
|
34
|
-
t.integer '
|
34
|
+
t.integer 'page_id'
|
35
|
+
t.integer 'node_id'
|
35
36
|
t.timestamps
|
36
37
|
end
|
37
38
|
|
@@ -44,7 +45,10 @@ begin
|
|
44
45
|
end
|
45
46
|
|
46
47
|
ActiveRecord::Base.establish_connection(:adapter=>'sqlite3', :database=>':memory:')
|
47
|
-
ActiveRecord::
|
48
|
-
|
49
|
-
|
48
|
+
ActiveRecord::Base.logger = Logger.new(File.open(Pathname(__FILE__).dirname + 'test.log', 'wb'))
|
49
|
+
#if !ActiveRecord::Base.connection.table_exists?('pages')
|
50
|
+
ActiveRecord::Migration.verbose = false
|
51
|
+
VersionsMigration.migrate(:up)
|
52
|
+
ActiveRecord::Migration.verbose = true
|
53
|
+
#end
|
50
54
|
end
|
Binary file
|
Binary file
|
data/test/helper.rb
CHANGED
@@ -1,16 +1,48 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'test/unit'
|
3
3
|
require 'shoulda'
|
4
|
+
require 'active_record'
|
5
|
+
require 'active_support'
|
6
|
+
require 'active_support/testing/assertions'
|
7
|
+
require 'tempfile'
|
4
8
|
|
5
9
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
6
10
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
7
11
|
|
8
|
-
require 'active_record'
|
9
|
-
require 'active_support'
|
10
|
-
require 'active_support/testing/assertions'
|
11
12
|
require 'versions'
|
12
13
|
require 'fixtures'
|
13
14
|
|
14
15
|
class Test::Unit::TestCase
|
15
16
|
include ActiveSupport::Testing::Assertions
|
17
|
+
FILE_FIXTURES_PATH = Pathname(__FILE__).dirname + 'fixtures/files'
|
18
|
+
|
19
|
+
# taken from http://manuals.rubyonrails.com/read/chapter/28#page237 with some modifications
|
20
|
+
def uploaded_fixture(fname, content_type="application/octet-stream", filename=nil)
|
21
|
+
path = File.join(FILE_FIXTURES_PATH, fname)
|
22
|
+
filename ||= File.basename(path)
|
23
|
+
# simulate small files with StringIO
|
24
|
+
if File.stat(path).size < 1024
|
25
|
+
# smaller then 1 Ko
|
26
|
+
t = StringIO.new(File.read(path))
|
27
|
+
else
|
28
|
+
t = Tempfile.new(fname)
|
29
|
+
FileUtils.copy_file(path, t.path)
|
30
|
+
end
|
31
|
+
uploaded_file(t, filename, content_type)
|
32
|
+
end
|
33
|
+
|
34
|
+
# JPEG helper
|
35
|
+
def uploaded_jpg(fname, filename=nil)
|
36
|
+
uploaded_fixture(fname, 'image/jpeg', filename)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def uploaded_file(file, filename = nil, content_type = nil)
|
41
|
+
(class << file; self; end;).class_eval do
|
42
|
+
alias local_path path if respond_to?(:path) # FIXME: do we need this ?
|
43
|
+
define_method(:original_filename) { filename }
|
44
|
+
define_method(:content_type) { content_type }
|
45
|
+
end
|
46
|
+
file
|
47
|
+
end
|
16
48
|
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class AfterCommitTest < Test::Unit::TestCase
|
4
|
+
class Page < ActiveRecord::Base
|
5
|
+
attr_accessor :actions
|
6
|
+
before_save :do_action
|
7
|
+
after_save :raise_to_rollback
|
8
|
+
validates_presence_of :name
|
9
|
+
|
10
|
+
def after_commit_actions
|
11
|
+
@after_commit_actions ||= []
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def do_action
|
16
|
+
after_commit do
|
17
|
+
after_commit_actions << 'executed'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def raise_to_rollback
|
22
|
+
raise ActiveRecord::Rollback if self[:name] == 'raise'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'Creating a valid page' do
|
27
|
+
should 'trigger after_commit once all is done' do
|
28
|
+
page = Page.create(:name => 'hello')
|
29
|
+
assert_equal ['executed'], page.after_commit_actions
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'inside a transaction' do
|
33
|
+
should 'trigger after_commit after last transaction' do
|
34
|
+
page = nil
|
35
|
+
Page.transaction do
|
36
|
+
page = Page.create(:name => 'hello')
|
37
|
+
assert_equal [], page.after_commit_actions
|
38
|
+
end
|
39
|
+
assert_equal ['executed'], page.after_commit_actions
|
40
|
+
end
|
41
|
+
|
42
|
+
should 'not trigger after_commit if outer transaction fails' do
|
43
|
+
page = nil
|
44
|
+
begin
|
45
|
+
Page.transaction do
|
46
|
+
page = Page.create(:name => 'hello')
|
47
|
+
assert_equal [], page.after_commit_actions
|
48
|
+
raise 'Something went bad'
|
49
|
+
end
|
50
|
+
rescue Exception => err
|
51
|
+
end
|
52
|
+
assert_equal [], page.after_commit_actions
|
53
|
+
end
|
54
|
+
|
55
|
+
should 'not trigger after_commit on rollback' do
|
56
|
+
page = nil
|
57
|
+
Page.transaction do
|
58
|
+
page = Page.create(:name => 'hello')
|
59
|
+
assert_equal [], page.after_commit_actions
|
60
|
+
raise ActiveRecord::Rollback
|
61
|
+
end
|
62
|
+
assert_equal [], page.after_commit_actions
|
63
|
+
end
|
64
|
+
|
65
|
+
should 'clear after_commit after transaction' do
|
66
|
+
actions = []
|
67
|
+
Page.transaction do
|
68
|
+
Page.after_commit do
|
69
|
+
actions << 'executed'
|
70
|
+
end
|
71
|
+
raise ActiveRecord::Rollback
|
72
|
+
end
|
73
|
+
assert_equal [], actions
|
74
|
+
|
75
|
+
Page.transaction do
|
76
|
+
end
|
77
|
+
assert_equal [], actions
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'Creating an invalid page' do
|
84
|
+
should 'not trigger after_commit' do
|
85
|
+
page = Page.create
|
86
|
+
assert page.new_record?
|
87
|
+
assert_equal [], page.after_commit_actions
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'Raising an error after save' do
|
92
|
+
should 'not trigger after_commit' do
|
93
|
+
page = Page.create(:name => 'raise')
|
94
|
+
assert_equal [], page.after_commit_actions
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
should 'not allow after commit outside of a transaction' do
|
99
|
+
assert_raise(Exception) do
|
100
|
+
Page.new.instance_eval do
|
101
|
+
after_commit do
|
102
|
+
# never executed
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -1,222 +1,260 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
#
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
#
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
#
|
172
|
-
#
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
3
|
+
class AttachmentTest < Test::Unit::TestCase
|
4
|
+
@@attachments_dir = (Pathname(__FILE__).dirname + 'tmp').expand_path
|
5
|
+
|
6
|
+
Attachment = Class.new(Versions::SharedAttachment) do
|
7
|
+
def filepath
|
8
|
+
File.join(@@attachments_dir, super)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Mock a version class with shared attachments (between versions of the same document)
|
13
|
+
class Version < ActiveRecord::Base
|
14
|
+
include Versions::Auto
|
15
|
+
end
|
16
|
+
|
17
|
+
# Mock a document class with many versions
|
18
|
+
class Document < ActiveRecord::Base
|
19
|
+
include Versions::Multi
|
20
|
+
has_multiple :versions, :class_name => 'AttachmentTest::Version', :inverse => 'node'
|
21
|
+
|
22
|
+
include Versions::Attachment
|
23
|
+
store_attachments_in :version, :class_name => 'AttachmentTest::Version', :attachment_class => 'AttachmentTest::Attachment'
|
24
|
+
|
25
|
+
set_table_name :pages
|
26
|
+
|
27
|
+
def title
|
28
|
+
version.title
|
29
|
+
end
|
30
|
+
|
31
|
+
def title=(t)
|
32
|
+
version.title = t
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def setup
|
38
|
+
FileUtils.rmtree(@@attachments_dir)
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'When creating a new owner' do
|
42
|
+
setup do
|
43
|
+
@owner = Version.create(:file => uploaded_jpg('bird.jpg'))
|
44
|
+
end
|
45
|
+
|
46
|
+
should 'store file in the filesystem' do
|
47
|
+
assert File.exist?(@owner.filepath)
|
48
|
+
assert_equal uploaded_jpg('bird.jpg').read, File.read(@owner.filepath)
|
49
|
+
end
|
50
|
+
|
51
|
+
should 'restore the filepath from the database' do
|
52
|
+
attachment = Attachment.find(@owner.attachment_id)
|
53
|
+
assert_equal @owner.filepath, attachment.filepath
|
54
|
+
end
|
55
|
+
|
56
|
+
should 'rename file on bad original_filename' do
|
57
|
+
file = uploaded_jpg('bird.jpg')
|
58
|
+
class << file
|
59
|
+
def original_filename
|
60
|
+
'../../bad.txt'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
owner = Version.create(:file => file)
|
64
|
+
assert File.exist?(owner.filepath)
|
65
|
+
assert_no_match %r{bird\.jpg}, owner.filepath
|
66
|
+
assert_no_match %r{bad\.txt}, owner.filepath
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'When the transaction fails' do
|
71
|
+
should 'not write file on create' do
|
72
|
+
owner = nil
|
73
|
+
filepath = nil
|
74
|
+
assert_difference('Attachment.count', 0) do
|
75
|
+
Version.transaction do
|
76
|
+
owner = Version.create(:file => uploaded_jpg('bird.jpg'))
|
77
|
+
filepath = owner.filepath
|
78
|
+
assert !File.exist?(filepath)
|
79
|
+
raise ActiveRecord::Rollback
|
80
|
+
end
|
81
|
+
end
|
82
|
+
assert !File.exist?(filepath)
|
83
|
+
end
|
84
|
+
|
85
|
+
should 'not remove file on destroy' do
|
86
|
+
@owner = Version.create(:file => uploaded_jpg('bird.jpg'))
|
87
|
+
filepath = @owner.filepath
|
88
|
+
assert File.exist?(filepath)
|
89
|
+
assert_difference('Attachment.count', 0) do
|
90
|
+
Version.transaction do
|
91
|
+
@owner.destroy
|
92
|
+
assert File.exist?(filepath)
|
93
|
+
raise ActiveRecord::Rollback
|
94
|
+
end
|
95
|
+
end
|
96
|
+
assert File.exist?(filepath)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'On an owner with a file' do
|
101
|
+
setup do
|
102
|
+
@owner = Version.create(:file => uploaded_jpg('bird.jpg'))
|
103
|
+
@owner = Version.find(@owner.id)
|
104
|
+
@owner.class_eval do
|
105
|
+
def should_clone?
|
106
|
+
false
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
should 'remove file in the filesystem when updating file' do
|
112
|
+
old_filepath = @owner.filepath
|
113
|
+
assert_difference('Attachment.count', 0) do # destroy + create
|
114
|
+
assert @owner.update_attributes(:file => uploaded_jpg('lake.jpg'))
|
115
|
+
end
|
116
|
+
assert_not_equal old_filepath, @owner.filepath
|
117
|
+
assert File.exist?(@owner.filepath)
|
118
|
+
assert_equal uploaded_jpg('lake.jpg').read, File.read(@owner.filepath)
|
119
|
+
assert !File.exist?(old_filepath)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
context 'Updating document' do
|
124
|
+
setup do
|
125
|
+
begin
|
126
|
+
@doc = Document.create(:title => 'birdy', :file => uploaded_jpg('bird.jpg'))
|
127
|
+
rescue => err
|
128
|
+
puts err.message
|
129
|
+
puts err.backtrace.join("\n")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Updating document ...attributes
|
134
|
+
context 'attributes' do
|
135
|
+
setup do
|
136
|
+
assert_difference('Version.count', 1) do
|
137
|
+
@doc.update_attributes(:title => 'hopla')
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
should 'reuse the same filepath in new versions' do
|
142
|
+
filepath = nil
|
143
|
+
@doc.versions.each do |version|
|
144
|
+
if filepath
|
145
|
+
assert_equal filepath, version.filepath
|
146
|
+
else
|
147
|
+
filepath = version.filepath
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Updating document ...file
|
154
|
+
context 'file' do
|
155
|
+
setup do
|
156
|
+
assert_difference('Version.count', 1) do
|
157
|
+
@doc.update_attributes(:file => uploaded_jpg('lake.jpg'))
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
should 'create new filepath' do
|
162
|
+
filepath = nil
|
163
|
+
@doc.versions.each do |version|
|
164
|
+
if filepath
|
165
|
+
assert_not_equal filepath, version.filepath
|
166
|
+
else
|
167
|
+
filepath = version.filepath
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end # Updating document .. file
|
172
|
+
end # Updating document
|
173
|
+
|
174
|
+
context 'On a document with many versions' do
|
175
|
+
setup do
|
176
|
+
assert_difference('Version.count', 2) do
|
177
|
+
@doc = Document.create(:title => 'birdy', :file => uploaded_jpg('bird.jpg'))
|
178
|
+
@doc.update_attributes(:title => 'Vögel')
|
179
|
+
@doc = Document.find(@doc.id)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
context 'removing a version' do
|
184
|
+
|
185
|
+
should 'not remove shared attachment' do
|
186
|
+
filepath = @doc.version.filepath
|
187
|
+
|
188
|
+
assert_difference('Version.count', -1) do
|
189
|
+
assert_difference('Attachment.count', 0) do
|
190
|
+
assert @doc.version.destroy
|
191
|
+
end
|
192
|
+
end
|
193
|
+
assert File.exist?(filepath)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
context 'removing the last version' do
|
198
|
+
|
199
|
+
should 'remove shared attachment' do
|
200
|
+
filepath = @doc.version.filepath
|
201
|
+
|
202
|
+
assert_difference('Version.count', -2) do
|
203
|
+
assert_difference('Attachment.count', -1) do
|
204
|
+
@doc.versions.each do |version|
|
205
|
+
assert version.destroy
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
assert !File.exist?(filepath)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
context 'A module using attachments without versions' do
|
215
|
+
class Doc < ActiveRecord::Base
|
216
|
+
set_table_name :versions
|
217
|
+
include Versions::Attachment
|
218
|
+
store_attachments_in self, :attachment_class => 'AttachmentTest::Attachment'
|
219
|
+
end
|
220
|
+
|
221
|
+
subject do
|
222
|
+
Doc.create('file' => uploaded_jpg('bird.jpg'))
|
223
|
+
end
|
224
|
+
|
225
|
+
should 'accept files' do
|
226
|
+
assert !subject.new_record?
|
227
|
+
end
|
228
|
+
|
229
|
+
should 'store file in the filesystem' do
|
230
|
+
assert File.exist?(subject.filepath)
|
231
|
+
assert_equal uploaded_jpg('bird.jpg').read, File.read(subject.filepath)
|
232
|
+
end
|
233
|
+
|
234
|
+
should 'restore the filepath from the database' do
|
235
|
+
attachment = Attachment.find(subject.attachment_id)
|
236
|
+
assert_equal subject.filepath, attachment.filepath
|
237
|
+
end
|
238
|
+
|
239
|
+
should 'remove file in the filesystem when updating file' do
|
240
|
+
old_filepath = subject.filepath
|
241
|
+
assert_difference('Attachment.count', 0) do # destroy + create
|
242
|
+
assert subject.update_attributes(:file => uploaded_jpg('lake.jpg'))
|
243
|
+
end
|
244
|
+
assert_not_equal old_filepath, subject.filepath
|
245
|
+
assert File.exist?(subject.filepath)
|
246
|
+
assert_equal uploaded_jpg('lake.jpg').read, File.read(subject.filepath)
|
247
|
+
assert !File.exist?(old_filepath)
|
248
|
+
end
|
249
|
+
|
250
|
+
should 'create and destroy objects without attachments' do
|
251
|
+
assert Doc.create.destroy
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
private
|
256
|
+
def filepath(attachment_id, filename)
|
257
|
+
digest = Digest::SHA1.hexdigest(attachment_id.to_s)
|
258
|
+
"#{digest[0..0]}/#{digest[1..1]}/#{filename}"
|
259
|
+
end
|
260
|
+
end
|