carrierwave-meta 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +173 -15
- data/carrierwave-meta.gemspec +2 -0
- data/lib/carrierwave-meta.rb +1 -0
- data/lib/carrierwave-meta/meta.rb +8 -1
- data/lib/carrierwave-meta/version.rb +1 -1
- data/spec/modules/meta_spec.rb +4 -7
- data/spec/spec_helper.rb +2 -0
- data/spec/support/test_uploader.rb +5 -1
- metadata +40 -12
data/README.rdoc
CHANGED
@@ -11,14 +11,6 @@ Add the following line to your Gemfile:
|
|
11
11
|
include CarrierWave::RMagick
|
12
12
|
include CarrierWave::Meta
|
13
13
|
|
14
|
-
def store_dir
|
15
|
-
"tmp/store"
|
16
|
-
end
|
17
|
-
|
18
|
-
def cache_dir
|
19
|
-
"tmp/cache"
|
20
|
-
end
|
21
|
-
|
22
14
|
storage :file
|
23
15
|
|
24
16
|
process :store_meta
|
@@ -46,6 +38,9 @@ Add the following line to your Gemfile:
|
|
46
38
|
|
47
39
|
= Saving values to database
|
48
40
|
|
41
|
+
Simply create database columns to hold metadata in your model's table. Currently gem supports width, height,
|
42
|
+
image_size ([width, height]), content_type and file_size fields. Versions are supported too.
|
43
|
+
|
49
44
|
class TestModel
|
50
45
|
attr_accessor :image_width
|
51
46
|
attr_accessor :image_height
|
@@ -74,25 +69,188 @@ When columns are available in the model instance, metadata is stored in that col
|
|
74
69
|
|
75
70
|
= Behind the scenes
|
76
71
|
|
77
|
-
After the file is retrieved from store metadata is recalculated
|
78
|
-
attached model instance.
|
72
|
+
After the file is retrieved from store or cache metadata is recalculated unless uploader has attached
|
73
|
+
model instance. If uploader has attached model instance values are read from that instance.
|
79
74
|
|
80
75
|
uploader = TestUploader.new
|
81
76
|
uploader.retrieve_from_store!('test.jpg')
|
82
77
|
|
83
78
|
uploader.version.width # 200
|
84
79
|
|
85
|
-
|
86
|
-
|
80
|
+
= model_delegate_attribute
|
81
|
+
|
82
|
+
Used to synchronize data between uploader and mounted model instance. Model's instance is used like value cache.
|
83
|
+
|
84
|
+
class DelegateTestModel
|
85
|
+
attr_accessor :processed
|
86
|
+
attr_accessor :a_processed
|
87
|
+
attr_accessor :a_b_processed
|
88
|
+
end
|
89
|
+
|
90
|
+
class DelegateTestUploader < CarrierWave::Uploader::Base
|
91
|
+
model_delegate_attribute :uploaded
|
92
|
+
|
93
|
+
set_processed
|
94
|
+
|
95
|
+
version :a do
|
96
|
+
set_processed
|
97
|
+
version :b do
|
98
|
+
set_processed
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def set_processed
|
103
|
+
self.processed = true
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
model = DelegateTestModel.new
|
108
|
+
uploader = DelegateTestUploader.new(model, :image)
|
109
|
+
file = File.open('test.jpg')
|
110
|
+
|
111
|
+
uploader.store!(file)
|
112
|
+
|
113
|
+
model.processed # true
|
114
|
+
model.a_processed # true
|
115
|
+
model.a_b_processed # true
|
116
|
+
|
117
|
+
model.a_processed = false
|
118
|
+
|
119
|
+
uploader.processed # true
|
120
|
+
uploader.a_processed # false
|
121
|
+
uploader.a_b_processed # true
|
122
|
+
|
123
|
+
When model is mounted to uploader:
|
124
|
+
|
125
|
+
1. If attribute is assigned inside uploader then corresponding property in model is also assigned.
|
126
|
+
2. If attribute is retrieved from uploader instance first checks that value is defined in model and returns it. Otherwise returns uploader's instance variable.
|
127
|
+
3. If file is deleted, value becomes nil.
|
128
|
+
|
129
|
+
Otherwise acts as regular uploader's instance variables.
|
130
|
+
|
131
|
+
This is very useful for:
|
132
|
+
|
133
|
+
= Integrating CarrierWave with JCrop
|
134
|
+
|
135
|
+
Let implement the behavior like at this demo: http://deepliquid.com/projects/Jcrop/demos.php?demo=thumbnail
|
136
|
+
|
137
|
+
The uploader:
|
138
|
+
|
139
|
+
class CropUploader < SobakaUploader
|
140
|
+
include CarrierWave::Meta
|
141
|
+
|
142
|
+
# Crop source is a source image converted from original which could be bigger than source area (left image in the example).
|
143
|
+
version :crop_source do
|
144
|
+
process :resize_to_fit => [300, 300]
|
145
|
+
process :store_meta
|
146
|
+
|
147
|
+
# This is the cropped version of parent image. Let crop to 50x50 square.
|
148
|
+
version :crop do
|
149
|
+
process :crop_to => [50, 50]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Defines crop area dimensions.
|
154
|
+
# This should be assigned before #store! and #cache! called and should be saved in the model's instance.
|
155
|
+
# Otherwise cropped image would be lost after #recreate_versions! is called.
|
156
|
+
# If crop area dimensions are'nt assigned, uploader calculates crop area dimensions inside the
|
157
|
+
# parent image and creates the default image.
|
158
|
+
model_delegate_attribute :x
|
159
|
+
model_delegate_attribute :y
|
160
|
+
model_delegate_attribute :w
|
161
|
+
model_delegate_attribute :h
|
162
|
+
|
163
|
+
# Crop processor
|
164
|
+
def crop_to(width, height)
|
165
|
+
# Checks that crop area is defined and crop should be done.
|
166
|
+
if ((crop_args[0] == crop_args[2]) || (crop_args[1] == crop_args[3]))
|
167
|
+
# If not creates default image and saves it's dimensions.
|
168
|
+
resize_to_fill_and_save_dimensions(width, height)
|
169
|
+
else
|
170
|
+
args = crop_args + [width, height]
|
171
|
+
crop_and_resize(*args)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def crop_and_resize(x, y, width, height, new_width, new_height)
|
176
|
+
manipulate! do |img|
|
177
|
+
cropped_img = img.crop(x, y, width, height)
|
178
|
+
new_img = cropped_img.resize_to_fill(new_width, new_height)
|
179
|
+
destroy_image(cropped_img)
|
180
|
+
destroy_image(img)
|
181
|
+
new_img
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Creates the default crop image.
|
186
|
+
# Here the original crop area dimensions are restored and assigned to the model's instance.
|
187
|
+
def resize_to_fill_and_save_dimensions(new_width, new_height)
|
188
|
+
manipulate! do |img|
|
189
|
+
width, height = img.columns, img.rows
|
190
|
+
new_img = img.resize_to_fill(new_width, new_height)
|
191
|
+
destroy_image(img)
|
192
|
+
|
193
|
+
w_ratio = width.to_f / new_width.to_f
|
194
|
+
h_ratio = height.to_f / new_height.to_f
|
195
|
+
|
196
|
+
ratio = [w_ratio, h_ratio].min
|
197
|
+
|
198
|
+
self.w = ratio * new_width
|
199
|
+
self.h = ratio * new_height
|
200
|
+
self.x = (width - self.w) / 2
|
201
|
+
self.y = (height - self.h) / 2
|
202
|
+
|
203
|
+
new_img
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
private
|
208
|
+
def crop_args
|
209
|
+
%w(x y w h).map { |accessor| send(accessor).to_i }
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Post should have :crop_source_version_x, :crop_source_version_y, :crop_source_version_h, :crop_source_version_w columns
|
214
|
+
class Post < ActiveRecord::Base
|
215
|
+
mount_uploader CropUploader, :image
|
216
|
+
end
|
217
|
+
|
218
|
+
# Let's upload an image
|
219
|
+
post = Post.new
|
220
|
+
post.image = params[:image] # Let the uploaded file is 800x600 JPEG
|
221
|
+
post.save!
|
222
|
+
|
223
|
+
post.image.crop_source.width # 300
|
224
|
+
post.image.crop_source.height # 200
|
225
|
+
post.image.crop_source.crop.width # 50
|
226
|
+
post.image.crop_source.crop.height # 50
|
227
|
+
|
228
|
+
# Default crop area coordinates within the limits of big image dimensions: square at the center of an image
|
229
|
+
post.image.crop_source.crop.x # 50
|
230
|
+
post.image.crop_source.crop.y # 50
|
231
|
+
post.image.crop_source.crop.w # 200
|
232
|
+
post.image.crop_source.crop.h # 200
|
233
|
+
|
234
|
+
# Let user change the crop area with JCrop script. Pass new crop area parameters to the model.
|
235
|
+
post.crop_source_crop_x = 100
|
236
|
+
post.crop_source_crop_y = 100
|
237
|
+
post.crop_source_crop_w = 100
|
238
|
+
post.crop_source_crop_h = 100
|
239
|
+
|
240
|
+
post.save! # Crop image is reprocessed
|
241
|
+
|
242
|
+
post.image.crop_source.crop.width # 50
|
243
|
+
post.image.crop_source.crop.height # 50
|
87
244
|
|
88
|
-
|
245
|
+
= A note about testing
|
89
246
|
|
90
|
-
|
247
|
+
fschwahn added support for mini-magick. To run tests with mini-magick do:
|
91
248
|
|
249
|
+
bundle exec rake spec PROCESSOR=mini_magick
|
92
250
|
|
93
251
|
= TODO
|
94
252
|
|
95
253
|
1. I do not know how it would work with S3 and other remote storages. Should be tested.
|
96
254
|
2. Write integration guide for JCrop.
|
97
255
|
3. A notice about content-type.
|
98
|
-
4.
|
256
|
+
4. Improve my english skills.
|
data/carrierwave-meta.gemspec
CHANGED
@@ -20,8 +20,10 @@ Gem::Specification.new do |s|
|
|
20
20
|
|
21
21
|
s.add_dependency(%q<carrierwave>, [">= 0.5.7"])
|
22
22
|
s.add_dependency(%q<activesupport>, [">= 3.0"])
|
23
|
+
s.add_dependency(%q<mime-types>)
|
23
24
|
s.add_development_dependency(%q<rspec-rails>, ">= 2.6")
|
24
25
|
s.add_development_dependency(%q<sqlite3-ruby>)
|
25
26
|
s.add_development_dependency(%q<rmagick>)
|
27
|
+
s.add_development_dependency(%q<mini_magick>)
|
26
28
|
s.add_development_dependency(%q<mime-types>)
|
27
29
|
end
|
data/lib/carrierwave-meta.rb
CHANGED
@@ -37,6 +37,10 @@ module CarrierWave
|
|
37
37
|
set_content_type(true)
|
38
38
|
end
|
39
39
|
|
40
|
+
def image_size_s
|
41
|
+
image_size.join('x')
|
42
|
+
end
|
43
|
+
|
40
44
|
private
|
41
45
|
def call_store_meta(file = nil)
|
42
46
|
# Re-retrieve metadata for a file only if model is not present.
|
@@ -47,9 +51,12 @@ module CarrierWave
|
|
47
51
|
[].tap do |size|
|
48
52
|
if self.file.content_type =~ /image/
|
49
53
|
manipulate! do |img|
|
50
|
-
if img.is_a?(::Magick::Image)
|
54
|
+
if defined?(::Magick::Image) && img.is_a?(::Magick::Image)
|
51
55
|
size << img.columns
|
52
56
|
size << img.rows
|
57
|
+
elsif defined?(::MiniMagick::Image) && img.is_a?(::MiniMagick::Image)
|
58
|
+
size << img["width"]
|
59
|
+
size << img["height"]
|
53
60
|
else
|
54
61
|
raise "Only RMagick is supported yet. Fork and update it."
|
55
62
|
end
|
data/spec/modules/meta_spec.rb
CHANGED
@@ -13,14 +13,16 @@ describe TestUploader do
|
|
13
13
|
uploader.width.should eq(ORIGINAL_SIZE[0])
|
14
14
|
uploader.height.should eq(ORIGINAL_SIZE[1])
|
15
15
|
uploader.image_size.should eq(ORIGINAL_SIZE)
|
16
|
-
uploader.content_type.should eq(
|
16
|
+
uploader.content_type.should eq(MIME::Types.type_for(file_name).first.to_s)
|
17
17
|
uploader.file_size.should_not be_blank
|
18
|
+
uploader.image_size_s.should eq(ORIGINAL_SIZE.join('x'))
|
18
19
|
|
19
20
|
uploader.version.width.should eq(VERSION_SIZE[0])
|
20
21
|
uploader.version.height.should eq(VERSION_SIZE[1])
|
21
22
|
uploader.version.image_size.should eq(VERSION_SIZE)
|
22
|
-
uploader.version.content_type.should eq(
|
23
|
+
uploader.version.content_type.should eq(MIME::Types.type_for(file_name).first.to_s)
|
23
24
|
uploader.version.file_size.should_not be_blank
|
25
|
+
uploader.version.image_size_s.should eq(VERSION_SIZE.join('x'))
|
24
26
|
end
|
25
27
|
|
26
28
|
def obj_values_are_correct(obj)
|
@@ -37,11 +39,6 @@ describe TestUploader do
|
|
37
39
|
obj.image_version_file_size.should_not be_blank
|
38
40
|
end
|
39
41
|
|
40
|
-
def uploader_values_are_correct(obj)
|
41
|
-
obj.width.should eq(ORIGINAL_SIZE[0])
|
42
|
-
obj.height.should eq(ORIGINAL_SIZE[1])
|
43
|
-
end
|
44
|
-
|
45
42
|
context "detached uploader" do
|
46
43
|
it "stores metadata after cache!" do
|
47
44
|
uploader = TestUploader.new
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: carrierwave-meta
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Victor Sokolov
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-10-14 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: carrierwave
|
@@ -49,9 +49,23 @@ dependencies:
|
|
49
49
|
type: :runtime
|
50
50
|
version_requirements: *id002
|
51
51
|
- !ruby/object:Gem::Dependency
|
52
|
-
name:
|
52
|
+
name: mime-types
|
53
53
|
prerelease: false
|
54
54
|
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
type: :runtime
|
64
|
+
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: rspec-rails
|
67
|
+
prerelease: false
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
55
69
|
none: false
|
56
70
|
requirements:
|
57
71
|
- - ">="
|
@@ -62,11 +76,11 @@ dependencies:
|
|
62
76
|
- 6
|
63
77
|
version: "2.6"
|
64
78
|
type: :development
|
65
|
-
version_requirements: *
|
79
|
+
version_requirements: *id004
|
66
80
|
- !ruby/object:Gem::Dependency
|
67
81
|
name: sqlite3-ruby
|
68
82
|
prerelease: false
|
69
|
-
requirement: &
|
83
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
70
84
|
none: false
|
71
85
|
requirements:
|
72
86
|
- - ">="
|
@@ -76,11 +90,11 @@ dependencies:
|
|
76
90
|
- 0
|
77
91
|
version: "0"
|
78
92
|
type: :development
|
79
|
-
version_requirements: *
|
93
|
+
version_requirements: *id005
|
80
94
|
- !ruby/object:Gem::Dependency
|
81
95
|
name: rmagick
|
82
96
|
prerelease: false
|
83
|
-
requirement: &
|
97
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
84
98
|
none: false
|
85
99
|
requirements:
|
86
100
|
- - ">="
|
@@ -90,11 +104,25 @@ dependencies:
|
|
90
104
|
- 0
|
91
105
|
version: "0"
|
92
106
|
type: :development
|
93
|
-
version_requirements: *
|
107
|
+
version_requirements: *id006
|
108
|
+
- !ruby/object:Gem::Dependency
|
109
|
+
name: mini_magick
|
110
|
+
prerelease: false
|
111
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
hash: 3
|
117
|
+
segments:
|
118
|
+
- 0
|
119
|
+
version: "0"
|
120
|
+
type: :development
|
121
|
+
version_requirements: *id007
|
94
122
|
- !ruby/object:Gem::Dependency
|
95
123
|
name: mime-types
|
96
124
|
prerelease: false
|
97
|
-
requirement: &
|
125
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
98
126
|
none: false
|
99
127
|
requirements:
|
100
128
|
- - ">="
|
@@ -104,7 +132,7 @@ dependencies:
|
|
104
132
|
- 0
|
105
133
|
version: "0"
|
106
134
|
type: :development
|
107
|
-
version_requirements: *
|
135
|
+
version_requirements: *id008
|
108
136
|
description: ""
|
109
137
|
email:
|
110
138
|
- gzigzigzi@gmail.com
|