file_model 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rake_ext'
2
+
3
+ project(
4
+ name: "file_model",
5
+ gem: true,
6
+ summary: "File storage for Models",
7
+
8
+ author: "Alexey Petrushin",
9
+ homepage: "http://github.com/alexeypetrushin/file_model"
10
+ )
@@ -0,0 +1,120 @@
1
+ module FileModel
2
+ attr_reader :name
3
+
4
+ def read name
5
+ self.name = name
6
+ end
7
+
8
+ attr_reader :original
9
+ def original= something
10
+ @original = something.to_file
11
+ raise "file #{original.name} not exist!" if original and !original.exist?
12
+ original
13
+ end
14
+
15
+ def save
16
+ return false unless original
17
+
18
+ # validating
19
+ validate
20
+ raise "can't save invalid file!" unless errors.empty?
21
+
22
+ # deleting old files
23
+ destroy validate: false
24
+
25
+ # saving
26
+ self.name = build_name original.name
27
+ process do |file|
28
+ path = build_path name
29
+ file.copy_to self.class.box[path]
30
+ end
31
+
32
+ self.class.versions.each do |version_name, klass|
33
+ version = send version_name
34
+ version.process do |file|
35
+ path = build_path name, version_name
36
+ file.copy_to self.class.box[path]
37
+ end
38
+ end
39
+
40
+ true
41
+ end
42
+
43
+ def destroy options = {}
44
+ return false unless name
45
+
46
+ unless options[:validate] == false
47
+ self.validate
48
+ raise "can't save invalid file!" unless errors.empty?
49
+ end
50
+
51
+ path = build_path name
52
+ self.class.box[path].destroy
53
+
54
+ self.class.versions.each do |version_name, klass|
55
+ path = build_path name, version_name
56
+ self.class.box[path].destroy
57
+ end
58
+
59
+ true
60
+ end
61
+
62
+ def url
63
+ name && build_url(name)
64
+ end
65
+
66
+ def file
67
+ name && self.class.box[build_path(name)]
68
+ end
69
+
70
+ def process &block
71
+ block.call original
72
+ end
73
+
74
+ def build_path name, version = nil
75
+ base, extension = name.rsplit('.', 2)
76
+ %(/#{base}#{".#{version}" if version}#{".#{extension}" if extension})
77
+ end
78
+
79
+ def build_url name, version = nil
80
+ base, extension = name.rsplit('.', 2)
81
+ %(/#{base}#{".#{version}" if version}#{".#{extension}" if extension})
82
+ end
83
+
84
+ def build_name name
85
+ name
86
+ end
87
+
88
+ def errors
89
+ @errors ||= []
90
+ end
91
+
92
+ def validate
93
+ errors.clear
94
+ # errors << "file #{original.name} not exist!" if original and !original.file?
95
+ end
96
+
97
+ protected
98
+ attr_writer :name
99
+
100
+ module ClassMethods
101
+ inheritable_accessor :versions, {}
102
+
103
+ def version name, &block
104
+ klass = Class.new FileModel::Version, &block
105
+ versions[name] = klass
106
+
107
+ iv_name = :"@#{name}"
108
+ define_method name do
109
+ unless version = instance_variable_get(iv_name)
110
+ version = klass.new self, name
111
+ instance_variable_set iv_name, version
112
+ end
113
+ version
114
+ end
115
+ end
116
+
117
+ attr_accessor :box
118
+ attr_required :box
119
+ end
120
+ end
@@ -0,0 +1,5 @@
1
+ respond_to? :fake_gem do
2
+ gem 'ruby_ext'
3
+ gem 'vfs'
4
+ gem 'vos'
5
+ end
@@ -0,0 +1,28 @@
1
+ module FileModel::Helper
2
+ module ClassMethods
3
+ def mount_file attr_name, file_model_class
4
+ attr_name.must_be.a Symbol
5
+ iv_name = :"@_#{attr_name}"
6
+
7
+ define_method attr_name do
8
+ unless file_model = instance_variable_get(iv_name)
9
+ file_name = attribute_get attr_name
10
+
11
+ file_model = file_model_class.new
12
+ file_model.read file_name
13
+
14
+ instance_variable_set iv_name, file_model
15
+ end
16
+ file_model
17
+ end
18
+
19
+ define_method :"#{attr_name}=" do |file|
20
+ file_model = send(attr_name)
21
+ file_model.original = file
22
+
23
+ file_name = file_model.build_name file_model.original.name
24
+ attribute_set attr_name, file_name
25
+ end
26
+ end
27
+ end
28
+ end
File without changes
@@ -0,0 +1,93 @@
1
+ require 'vos'
2
+
3
+ shared_examples_for 'file model crud' do
4
+ describe "file model crud" do
5
+ before :all do
6
+ class ImageFile
7
+ inherit FileModel
8
+
9
+ def build_path name, version = nil
10
+ '/storage/images' + super
11
+ end
12
+
13
+ def build_url name, version = nil
14
+ '/images' + super
15
+ end
16
+
17
+ def validate
18
+ super
19
+ errors << 'invalid name' if original and (original.name =~ /invalid/)
20
+ end
21
+ end
22
+ end
23
+ after(:all){remove_constants :ImageFile}
24
+
25
+ before do
26
+ require 'tempfile'
27
+ @tmp_dir = Dir.tmpdir.to_dir.dir('/shard_crud').create.path
28
+ @shared_dir = __FILE__.to_entry.parent.dir(:shared_crud).path
29
+
30
+ ImageFile.box = Vos::Box.new(Vos::Drivers::Local.new(@tmp_dir))
31
+ end
32
+ after do
33
+ @tmp_dir.to_dir.destroy
34
+ end
35
+
36
+ it 'CRUD' do
37
+ # read
38
+ model = @model_class.new
39
+ model.image.class.should == ImageFile
40
+ model.image.url.should be_nil
41
+ model.image.file.should be_nil
42
+
43
+ # preserving
44
+ file = "#{@shared_dir}/ship.jpg"
45
+ model.image = file
46
+ model.image.original.path.should == "#{@shared_dir}/ship.jpg"
47
+ model.instance_variable_get(:@image).should == 'ship.jpg'
48
+ model.image.url.should be_nil
49
+ model.image.file.should be_nil
50
+
51
+ # saving
52
+ model.save.should be_true
53
+ model.instance_variable_get(:@image).should == 'ship.jpg'
54
+ model.image.url.should == '/images/ship.jpg'
55
+ model.image.file.path.should == "/storage/images/ship.jpg"
56
+ "#{@tmp_dir}/storage/images/ship.jpg".to_file.exist?.should be_true
57
+
58
+ # reading
59
+ model = @model_class.new
60
+ model.instance_variable_set(:@image, 'ship.jpg')
61
+ model.image.url.should == '/images/ship.jpg'
62
+ model.image.file.path.should == "/storage/images/ship.jpg"
63
+
64
+ # updating
65
+ file2 = "#{@shared_dir}/ship2.jpg".to_file
66
+ model.image = file2
67
+ model.instance_variable_get(:@image).should == 'ship2.jpg'
68
+ model.image.original.path.should == "#{@shared_dir}/ship2.jpg"
69
+ model.image.url.should == '/images/ship.jpg'
70
+ model.image.file.path.should == "/storage/images/ship.jpg"
71
+
72
+ model.save.should be_true
73
+ model.instance_variable_get(:@image).should == 'ship2.jpg'
74
+ model.image.url.should == '/images/ship2.jpg'
75
+ model.image.file.path.should == "/storage/images/ship2.jpg"
76
+ "#{@tmp_dir}/storage/images/ship.jpg".to_file.exist?.should be_false
77
+ "#{@tmp_dir}/storage/images/ship2.jpg".to_file.exist?.should be_true
78
+
79
+ # destroying
80
+ model.destroy.should be_true
81
+ "#{@tmp_dir}/storage/images/ship2.jpg".to_file.exist?.should be_false
82
+ end
83
+
84
+ it "should be able to submit errors and interrupt model saving" do
85
+ file = "#{@shared_dir}/invalid.txt".to_file
86
+ model = @model_class.new
87
+ model.image = file
88
+ model.image.should_not_receive(:save)
89
+ model.save.should be_false
90
+ model.errors[:image].should == ['invalid name']
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,22 @@
1
+ class FileModel::Version
2
+ def initialize model, version_name
3
+ @model, @version_name = model, version_name
4
+ end
5
+
6
+ def url
7
+ name && build_url(name, version_name)
8
+ end
9
+
10
+ def file
11
+ name && model.class.box[build_path(name, version_name)]
12
+ end
13
+
14
+ def process &block
15
+ block.call original
16
+ end
17
+
18
+ protected
19
+ attr_reader :model, :version_name
20
+
21
+ delegate :original, :name, :build_path, :build_url, to: :model
22
+ end
data/lib/file_model.rb ADDED
@@ -0,0 +1,11 @@
1
+ module FileModel; end
2
+
3
+ require 'vfs'
4
+ require 'ruby_ext'
5
+
6
+ %w(
7
+ gems
8
+ version
9
+ helper
10
+ file_model
11
+ ).each{|f| require "file_model/#{f}"}
data/readme.md ADDED
@@ -0,0 +1 @@
1
+ File storage for Models
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+ require 'file_model/spec/shared_crud'
3
+
4
+ describe 'Model Integration' do
5
+ it_should_behave_like "file model crud"
6
+
7
+ before :all do
8
+ class ImageFile; end
9
+
10
+ class TheModel < ModelStub
11
+ inherit FileModel::Helper
12
+
13
+ mount_file :image, ImageFile
14
+
15
+ def attribute_get name
16
+ instance_variable_get :"@#{name}"
17
+ end
18
+ def attribute_set name, value
19
+ instance_variable_set :"@#{name}", value
20
+ end
21
+
22
+ before_validate.push -> _self {
23
+ self.image.validate
24
+ self.errors[:image] = image.errors unless image.errors.empty?
25
+ }
26
+
27
+ after_save.push -> _self {self.image.save}
28
+
29
+ after_destroy.push -> _self {self.image.destroy}
30
+ end
31
+ end
32
+ after(:all){remove_constants :TheModel}
33
+
34
+ before{@model_class = TheModel}
35
+ end
File without changes
File without changes
Binary file
Binary file
@@ -0,0 +1,174 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe "File Model" do
6
+ with_tmp_spec_dir
7
+
8
+ before :all do
9
+ class ImageFile
10
+ inherit FileModel
11
+
12
+ def process &block
13
+ block.call original
14
+ end
15
+
16
+ version :icon do
17
+ def process &block
18
+ block.call original
19
+ end
20
+ end
21
+
22
+ def build_path name, version = nil
23
+ '/storage/images' + super
24
+ end
25
+
26
+ def build_url name, version = nil
27
+ '/images' + super
28
+ end
29
+
30
+ def build_name name
31
+ name.gsub '+', ' '
32
+ end
33
+
34
+ def validate
35
+ super
36
+ errors << 'invalid name' if original and original.name =~ /invalid/
37
+ end
38
+ end
39
+ end
40
+ after(:all){remove_constants :ImageFile, :AvatarFile}
41
+
42
+ before do
43
+ ImageFile.box = Vos::Box.new(Vos::Drivers::Local.new(spec_dir))
44
+ @file = "#{spec_dir}/ship.jpg".to_file
45
+ end
46
+
47
+ it "CRUD" do
48
+ # blank
49
+ image = ImageFile.new
50
+ image.name.should be_nil
51
+ image.file.should be_nil
52
+ image.url.should == nil
53
+ image.icon.file.should be_nil
54
+ image.icon.url.should == nil
55
+
56
+ # preserving
57
+ image.original = @file
58
+ image.original.name.should == 'ship.jpg'
59
+ image.original.path.should == "#{spec_dir}/ship.jpg"
60
+
61
+ image.name.should be_nil
62
+ image.file.should be_nil
63
+ image.url.should == nil
64
+
65
+ image.icon.original.should == image.original
66
+ image.icon.name.should be_nil
67
+ image.icon.file.should be_nil
68
+ image.icon.url.should == nil
69
+
70
+ # writing
71
+ image.save.should be_true
72
+ image.name.should == 'ship.jpg'
73
+ image.file.name.should == 'ship.jpg'
74
+ image.file.path.should == "/storage/images/ship.jpg"
75
+ image.file.exist?.should be_true
76
+ image.url.should == '/images/ship.jpg'
77
+
78
+ image.icon.name.should == 'ship.jpg'
79
+ image.icon.file.name.should == 'ship.icon.jpg'
80
+ image.icon.file.path.should == "/storage/images/ship.icon.jpg"
81
+ image.icon.file.exist?.should be_true
82
+ image.icon.url.should == '/images/ship.icon.jpg'
83
+
84
+ # reading
85
+ image = ImageFile.new
86
+ image.read 'ship.jpg'
87
+ image.name.should == 'ship.jpg'
88
+
89
+ image.file.name.should == 'ship.jpg'
90
+ image.file.path.should == "/storage/images/ship.jpg"
91
+ image.file.exist?.should be_true
92
+ image.url.should == '/images/ship.jpg'
93
+
94
+ image.name.should == 'ship.jpg'
95
+ image.icon.file.name.should == 'ship.icon.jpg'
96
+ image.icon.file.path.should == "/storage/images/ship.icon.jpg"
97
+ image.icon.file.exist?.should be_true
98
+ image.icon.url.should == '/images/ship.icon.jpg'
99
+
100
+ # updating
101
+ image.original = "#{spec_dir}/ship2.jpg".to_file
102
+ image.original.name.should == 'ship2.jpg'
103
+ image.original.path.should == "#{spec_dir}/ship2.jpg"
104
+
105
+ image.save.should be_true
106
+
107
+ "#{spec_dir}/storage/images/ship.jpg".to_file.exist?.should be_false
108
+ "#{spec_dir}/storage/images/ship.icon.jpg".to_file.exist?.should be_false
109
+
110
+ image.name.should == 'ship2.jpg'
111
+ image.file.name.should == 'ship2.jpg'
112
+ image.file.path.should == "/storage/images/ship2.jpg"
113
+ image.file.exist?.should be_true
114
+ image.url.should == '/images/ship2.jpg'
115
+
116
+ image.icon.name.should == 'ship2.jpg'
117
+ image.icon.file.name.should == 'ship2.icon.jpg'
118
+ image.icon.file.path.should == "/storage/images/ship2.icon.jpg"
119
+ image.icon.file.exist?.should be_true
120
+ image.icon.url.should == '/images/ship2.icon.jpg'
121
+
122
+ # destroying
123
+ image = ImageFile.new
124
+ image.read 'ship2.jpg'
125
+ image.destroy
126
+ "#{spec_dir}/storage/images/ship2.jpg".to_file.exist?.should be_false
127
+ "#{spec_dir}/storage/images/ship2.icon.jpg".to_file.exist?.should be_false
128
+ end
129
+
130
+ it "should preserve spaces and unicode characters in filename" do
131
+ image = ImageFile.new
132
+ image.original = "#{spec_dir}/файл с пробелами.txt".to_file
133
+ image.save.should be_true
134
+ image.url.should =~ /\/файл с пробелами\.txt/
135
+ image.file.path.should =~ /\/файл с пробелами\.txt/
136
+ end
137
+
138
+ it "should escape + sign" do
139
+ image = ImageFile.new
140
+ image.original = "#{spec_dir}/file+name.txt".to_file
141
+ image.save.should be_true
142
+ image.url.should =~ /\/file name\.txt/
143
+ image.file.path.should =~ /\/file name\.txt/
144
+ end
145
+
146
+ it 'should raise error if file not exists' do
147
+ image = ImageFile.new
148
+ -> {image.original = "#{spec_dir}/non existing file.jpg".to_file}.should raise_error(/not exist/)
149
+ end
150
+
151
+ it 'should validate and not save invalid files' do
152
+ image = ImageFile.new
153
+ image.original = "#{spec_dir}/ship.jpg".to_file
154
+ image.validate
155
+ image.errors.should == []
156
+
157
+ image.original = "#{spec_dir}/invalid.txt".to_file
158
+ image.validate
159
+ image.errors.should == ['invalid name']
160
+
161
+ -> {image.save}.should raise_error(/invalid/)
162
+ end
163
+
164
+ it 'should accept anything responding to :to_file' do
165
+ file = "#{spec_dir}/ship.jpg".to_file
166
+
167
+ obj = mock
168
+ obj.should_receive(:to_file).and_return(file)
169
+
170
+ image = ImageFile.new
171
+ image.original = obj
172
+ image.original.should == file
173
+ end
174
+ end
@@ -0,0 +1,43 @@
1
+ class ModelStub
2
+ def errors
3
+ @errors ||= {}
4
+ end
5
+
6
+ def save
7
+ self.class.before_validate.each{|block| instance_eval &block}
8
+
9
+ if errors.empty?
10
+ # save
11
+ self.class.after_save.each{|block| instance_eval &block}
12
+ true
13
+ else
14
+ false
15
+ end
16
+ end
17
+
18
+ def destroy
19
+ self.class.before_validate.each{|block| instance_eval &block}
20
+
21
+ if errors.empty?
22
+ # save
23
+ self.class.after_destroy.each{|block| instance_eval &block}
24
+ true
25
+ else
26
+ false
27
+ end
28
+ end
29
+
30
+ class << self
31
+ def before_validate
32
+ @before_validate ||= []
33
+ end
34
+
35
+ def after_save
36
+ @after_save ||= []
37
+ end
38
+
39
+ def after_destroy
40
+ @after_destroy ||= []
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,5 @@
1
+ require 'file_model'
2
+ require 'vos'
3
+
4
+ require 'rspec_ext'
5
+ require 'spec_helper/model_stub'
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: file_model
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Alexey Petrushin
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-08-30 00:00:00.000000000Z
13
+ dependencies: []
14
+ description:
15
+ email:
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - Rakefile
21
+ - readme.md
22
+ - lib/file_model/file_model.rb
23
+ - lib/file_model/gems.rb
24
+ - lib/file_model/helper.rb
25
+ - lib/file_model/spec/shared_crud/invalid.txt
26
+ - lib/file_model/spec/shared_crud/ship.jpg
27
+ - lib/file_model/spec/shared_crud/ship2.jpg
28
+ - lib/file_model/spec/shared_crud.rb
29
+ - lib/file_model/version.rb
30
+ - lib/file_model.rb
31
+ - spec/abstract_model_spec.rb
32
+ - spec/file_model_spec/file+name.txt
33
+ - spec/file_model_spec/invalid.txt
34
+ - spec/file_model_spec/ship.jpg
35
+ - spec/file_model_spec/ship2.jpg
36
+ - spec/file_model_spec/файл с пробелами.txt
37
+ - spec/file_model_spec.rb
38
+ - spec/spec_helper/model_stub.rb
39
+ - spec/spec_helper.rb
40
+ homepage: http://github.com/alexeypetrushin/file_model
41
+ licenses: []
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubyforge_project:
60
+ rubygems_version: 1.8.6
61
+ signing_key:
62
+ specification_version: 3
63
+ summary: File storage for Models
64
+ test_files: []