file_model 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/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: []