cloudinary 1.0.15 → 1.0.16

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/README.md CHANGED
@@ -10,7 +10,7 @@ To install the Cloudinary Ruby GEM, run:
10
10
 
11
11
  $ gem install cloudinary
12
12
 
13
- I you use Rails 3.x or higher, edit your Gemfile, add the following line and run 'bundle'
13
+ If you use Rails 3.x or higher, edit your Gemfile, add the following line and run 'bundle'
14
14
 
15
15
  $ gem 'cloudinary'
16
16
 
@@ -18,17 +18,21 @@ Or in Rails 2.x, edit your environment.rb and add:
18
18
 
19
19
  $ config.gem 'cloudinary'
20
20
 
21
- If you would like to use our optional integration module of image uploads with ActiveRecord using CarrierWave, install CarrierWave to:
21
+ If you would like to use our optional integration module of image uploads with ActiveRecord using CarrierWave, install CarrierWave GEM:
22
22
 
23
23
  $ gem install carrierwave
24
24
 
25
25
  Rails 3.x Gemfile:
26
26
 
27
27
  $ gem 'carrierwave'
28
+ $ gem 'cloudinary'
28
29
 
29
30
  Rails 2.x environment.rb
30
31
 
31
32
  $ config.gem 'carrierwave', :version => '~> 0.4.1'
33
+ $ config.gem 'cloudinary'
34
+
35
+ Note: The CarrierWave GEM should be loaded before the Cloudinary GEM.
32
36
 
33
37
 
34
38
  ## Usage ######################################################################
@@ -1,319 +1,123 @@
1
1
  # Copyright Cloudinary
2
- require 'pp'
3
- module Cloudinary::CarrierWave
4
- CLOUDINARY_PATH = /^([^\/]+)\/upload\/v(\d+)\/([^\/]+)#([^\/]+)$/
5
-
6
- class UploadError < StandardError
7
- attr_reader :http_code
8
- def initialize(message, http_code)
9
- super(message)
10
- @http_code = http_code
11
- end
12
- end
13
-
14
- module ClassMethods
15
- def eager
16
- process :eager => true
17
- end
18
-
19
- def convert(format)
20
- process :convert => format
21
- end
22
-
23
- def resize_to_limit(width, height)
24
- process :resize_to_limit => [width, height]
25
- end
26
-
27
- def resize_to_fit(width, height)
28
- process :resize_to_fit => [width, height]
29
- end
30
-
31
- def resize_to_fill(width, height, gravity="Center")
32
- process :resize_to_fill => [width, height, gravity]
33
- end
2
+ require 'cloudinary/carrier_wave/process'
3
+ require 'cloudinary/carrier_wave/error'
4
+ require 'cloudinary/carrier_wave/remote'
5
+ require 'cloudinary/carrier_wave/preloaded'
6
+ require 'cloudinary/carrier_wave/storage'
34
7
 
35
- def resize_and_pad(width, height, background=:transparent, gravity="Center")
36
- process :resize_and_pad => [width, height, background, gravity]
37
- end
38
-
39
- def scale(width, height)
40
- process :scale => [width, height]
41
- end
42
-
43
- def crop(width, height, gravity="Center")
44
- process :crop => [width, height, gravity]
45
- end
46
-
47
- def cloudinary_transformation(options)
48
- process :cloudinary_transformation => options
49
- end
50
-
51
- def tags(*tags)
52
- process :tags=>tags
53
- end
54
- end
8
+ module Cloudinary::CarrierWave
55
9
 
56
10
  def self.included(base)
57
11
  base.storage Cloudinary::CarrierWave::Storage
58
12
  base.extend ClassMethods
59
13
  base.send(:attr_accessor, :metadata)
60
- base.send(:alias_method, :original_cache!, :cache!)
61
- base.send(:alias_method, :original_cache_name, :cache_name)
62
- base.send(:alias_method, :original_retrieve_from_cache!, :retrieve_from_cache!)
63
- base.send(:include, Override)
64
- end
65
-
66
- module Override
67
- def cache!(new_file)
68
- if new_file.is_a?(String) && new_file.match(CLOUDINARY_PATH)
69
- @file = CloudinaryFile.new(new_file)
70
- self.original_filename = @cache_id = @my_filename = @filename = @file.original_filename
71
- else
72
- original_cache!(new_file)
73
- end
74
- end
75
-
76
- def retrieve_from_cache!(new_file)
77
- if new_file.is_a?(String) && new_file.match(CLOUDINARY_PATH)
78
- @file = CloudinaryFile.new(new_file)
79
- self.original_filename = @cache_id = @my_filename = @filename = @file.original_filename
80
- else
81
- original_retrieve_from_cache!(new_file)
82
- end
83
- end
84
-
85
- def cache_name
86
- if @file.is_a?(CloudinaryFile)
87
- return @file.to_s
88
- else
89
- return original_cache_name
90
- end
91
- end
92
- end
93
-
94
- def set_or_yell(hash, attr, value)
95
- raise "conflicting transformation on #{attr} #{value}!=#{hash[attr]}" if hash[attr]
96
- hash[attr] = value
97
- end
98
-
99
- def transformation
100
- return @transformation if @transformation
101
- transformation = {}
102
- self.class.processors.each do
103
- |name, args|
104
- case name
105
- when :convert # Do nothing. This is handled by format
106
- when :resize_to_limit
107
- set_or_yell(transformation, :width, args[0])
108
- set_or_yell(transformation, :height, args[1])
109
- set_or_yell(transformation, :crop, :limit)
110
- when :resize_to_fit
111
- set_or_yell(transformation, :width, args[0])
112
- set_or_yell(transformation, :height, args[1])
113
- set_or_yell(transformation, :crop, :fit)
114
- when :resize_to_fill
115
- set_or_yell(transformation, :width, args[0])
116
- set_or_yell(transformation, :height, args[1])
117
- set_or_yell(transformation, :gravity, args[2].to_s.downcase)
118
- set_or_yell(transformation, :crop, :fill)
119
- when :resize_and_pad
120
- set_or_yell(transformation, :width, args[0])
121
- set_or_yell(transformation, :height, args[1])
122
- set_or_yell(transformation, :gravity, args[3].to_s.downcase)
123
- set_or_yell(transformation, :crop, :pad)
124
- when :scale
125
- set_or_yell(transformation, :width, args[0])
126
- set_or_yell(transformation, :height, args[1])
127
- set_or_yell(transformation, :crop, :scale)
128
- when :crop
129
- set_or_yell(transformation, :width, args[0])
130
- set_or_yell(transformation, :height, args[1])
131
- set_or_yell(transformation, :gravity, args[2].to_s.downcase)
132
- set_or_yell(transformation, :crop, :crop)
133
- when :cloudinary_transformation
134
- args.each do
135
- |attr, value|
136
- set_or_yell(transformation, attr, value)
137
- end
138
- end
139
- end
140
- @transformation = transformation
141
- @transformation
142
- end
143
-
144
- def eager
145
- @eager ||= self.class.processors.any?{|processor| processor[0] == :eager}
146
- end
147
-
148
- def tags
149
- @tags ||= self.class.processors.select{|processor| processor[0] == :tags}.map(&:last).first
150
- end
14
+ base.send(:attr_reader, :stored_version)
15
+
16
+ override_in_versions(base, :blank?, :full_public_id)
17
+ end
151
18
 
152
- def format
153
- format_processor = self.class.processors.find{|processor| processor[0] == :convert}
154
- if format_processor
155
- if format_processor[1].is_a?(Array)
156
- return format_processor[1][0]
157
- end
158
- return format_processor[1]
19
+ def retrieve_from_store!(identifier)
20
+ if identifier.blank?
21
+ @file = @stored_version = @stored_public_id = nil
22
+ self.original_filename = nil
23
+ else
24
+ @file = CloudinaryFile.new(identifier, self)
25
+ @public_id = @stored_public_id = @file.public_id
26
+ @stored_version = @file.version
27
+ self.original_filename = @file.filename
159
28
  end
160
- the_filename = original_filename || stored_filename
161
- return the_filename.split(".").last if the_filename.include?(".")
162
- "png" # TODO Default format?
163
- end
164
-
29
+ end
30
+
165
31
  def url(*args)
166
32
  if args.first && !args.first.is_a?(Hash)
167
33
  super
168
34
  else
169
- return nil if self.my_identifier.blank?
35
+ return super if self.blank?
170
36
  options = args.extract_options!
171
- options = options.merge(self.class.version_names.blank? ? {} : self.transformation)
172
- Cloudinary::Utils.cloudinary_url(self.my_filename, options)
37
+ options = self.transformation.merge(options) if self.version_name.present?
38
+ Cloudinary::Utils.cloudinary_url(self.full_public_id, {:format=>self.format}.merge(options))
173
39
  end
174
40
  end
41
+
42
+ def full_public_id
43
+ return nil if self.blank?
44
+ return self.my_public_id if self.stored_version.blank?
45
+ return "v#{self.stored_version}/#{self.my_public_id}"
46
+ end
175
47
 
176
- def recreate_versions!
177
- # Do nothing
178
- end
179
-
180
- def process!(new_file=nil)
181
- # Do nothing
182
- end
183
-
184
- def stored_filename
185
- model.read_uploader(mounted_as)
186
- end
187
-
188
- def my_filename
189
- return stored_filename if stored_filename.present?
190
- @my_filename ||= "#{self.public_id}.#{self.format}"
48
+ def filename
49
+ return nil if self.blank?
50
+ return [self.full_public_id, self.format].join(".")
191
51
  end
192
-
52
+
53
+ # public_id to use for uploaded file. Can be overridden by caller. Random public_id will be used otherwise.
193
54
  def public_id
194
- return @public_id if @public_id
195
- if stored_filename.present?
196
- last_dot = stored_filename.rindex(".")
197
- @public_id = last_dot ? stored_filename[0, last_dot] : stored_filename
198
- end
55
+ nil
56
+ end
57
+
58
+ # If the user overrode public_id, that should be used, even if it's different from current public_id in the database.
59
+ # Otherwise, try to use public_id from the database.
60
+ # Otherwise, generate a new random public_id
61
+ def my_public_id
62
+ @public_id ||= self.public_id
63
+ @public_id ||= @stored_public_id
199
64
  @public_id ||= Cloudinary::Utils.random_public_id
200
65
  end
201
-
202
- def download!(uri)
203
- uri = process_uri(uri)
204
- self.original_filename = @cache_id = @filename = File.basename(uri.path).gsub(/[^a-zA-Z0-9\.\-\+_]/, '')
205
- @file = RemoteFile.new(uri, @filename)
66
+
67
+ def recreate_versions!
68
+ # Do nothing
206
69
  end
207
70
 
208
- def my_identifier
209
- (self.filename || self.stored_filename.present?) ? self.my_filename : nil
71
+ def cache_versions!(new_file=nil)
72
+ # Do nothing
210
73
  end
211
74
 
212
- class RemoveableFile
213
- def initialize(identifier)
214
- @public_id = identifier.split("/").last.split(".").first
215
- end
216
-
217
- def delete
218
- Cloudinary::Uploader.destroy(@public_id)
219
- end
75
+ def process!(new_file=nil)
76
+ # Do nothing
220
77
  end
221
78
 
222
- class RemoteFile
223
- attr_reader :uri, :original_filename
224
- def initialize(uri, filename)
225
- @uri = uri
226
- @original_filename = filename
227
- end
228
-
229
- def delete
230
- # Do nothing. This is a virtual file.
231
- end
79
+ # Should removed files be removed from Cloudinary as well. Can be overridden.
80
+ def delete_remote?
81
+ true
232
82
  end
233
83
 
234
84
  class CloudinaryFile
235
- attr_reader :original_filename, :version
236
- def initialize(file_info)
237
- resource_type, @version, @original_filename, @signature = file_info.scan(CLOUDINARY_PATH).first
238
- raise "Cloudinary CarrierWave integration supports images only" if resource_type != "image"
239
- public_id = @original_filename[0..(@original_filename.rindex(".")-1)]
240
- expected_signature = Cloudinary::Utils.api_sign_request({:public_id=>public_id, :version=>version}, Cloudinary.config.api_secret)
241
- if @signature != expected_signature
242
- raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.cloudinary_signature_error", :public_id=>public_id, :default=>"Invalid signature for #{public_id}")
85
+ attr_reader :identifier, :public_id, :filename, :format, :version
86
+ def initialize(identifier, uploader)
87
+ @uploader = uploader
88
+ @identifier = identifier
89
+ if @identifier.include?("/")
90
+ version, @filename = @identifier.split("/")
91
+ @version = version[1..-1] # remove 'v' prefix
92
+ else
93
+ @filename = @identifier
94
+ @version = nil
243
95
  end
96
+ @public_id, @format = Cloudinary::CarrierWave.split_format(@filename)
244
97
  end
245
98
 
246
- def to_s
247
- "image/upload/v#{@version}/#{@original_filename}##{@signature}"
248
- end
249
-
250
99
  def delete
251
- # Do nothing. This is a virtual file.
100
+ Cloudinary::Uploader.destroy(self.public_id) if @uploader.delete_remote?
252
101
  end
253
102
  end
254
-
255
- class Storage < ::CarrierWave::Storage::Abstract
256
- def store!(file)
257
- # Moved to identifier...
258
- if uploader.class.version_names.blank?
259
- return store_cloudinary_version(file.version) if file.is_a?(CloudinaryFile)
260
-
261
- # This is the toplevel, need to upload the actual file.
262
- params = uploader.transformation.dup
263
- params[:return_error] = true
264
- params[:format] = uploader.format
265
- params[:public_id] = uploader.public_id.split("/").last
266
- params[:tags] = uploader.tags if uploader.tags
267
- eager_versions = uploader.versions.values.select(&:eager)
268
- params[:eager] = eager_versions.map{|version| [version.transformation, version.format]} if eager_versions.length > 0
269
-
270
- data = nil
271
- if (file.is_a?(RemoteFile))
272
- data = file.uri.to_s
273
- else
274
- data = file.file
275
- data.rewind if !file.is_path? && data.respond_to?(:rewind)
276
- end
277
- uploader.metadata = Cloudinary::Uploader.upload(data, params)
278
- if uploader.metadata["error"]
279
- raise UploadError.new(uploader.metadata["error"]["message"], uploader.metadata["error"]["http_code"])
280
- end
281
-
282
- store_cloudinary_version(uploader.metadata["version"]) if uploader.metadata["version"]
283
- # Will throw an exception on error
284
- else
285
- raise "nested versions are not allowed." if (uploader.class.version_names.length > 1)
286
- # Do nothing
287
- end
288
- nil
289
- end
290
-
291
- def store_cloudinary_version(version)
292
- name = "v#{version}/#{identifier.split("/").last}"
293
- model_class = uploader.model.class
294
- if defined?(ActiveRecord::Base) && uploader.model.is_a?(ActiveRecord::Base)
295
- primary_key = model_class.primary_key.to_sym
296
- model_class.update_all({uploader.mounted_as=>name}, {primary_key=>uploader.model.send(primary_key)})
297
- uploader.model.send :write_attribute, uploader.mounted_as, name
298
- elsif model_class.respond_to?(:update_all) && uploader.model.respond_to?(:_id)
299
- # Mongoid support
300
- model_class.where(:_id=>uploader.model._id).update_all(uploader.mounted_as=>name)
301
- uploader.model.send :write_attribute, uploader.mounted_as, name
302
- else
303
- raise "Only ActiveRecord and Mongoid are supported at the moment!"
103
+
104
+ def self.split_format(identifier)
105
+ last_dot = identifier.rindex(".")
106
+ return [public_id, nil] if last_dot.nil?
107
+ public_id = identifier[0, last_dot]
108
+ format = identifier[last_dot+1..-1]
109
+ return [public_id, format]
110
+ end
111
+
112
+ # For the given methods - versions should call the main uploader method
113
+ def self.override_in_versions(base, *methods)
114
+ methods.each do
115
+ |method|
116
+ base.send :define_method, method do
117
+ return super() if self.version_name.blank?
118
+ uploader = self.model.send(self.mounted_as)
119
+ uploader.send(method)
304
120
  end
305
- end
306
-
307
- def retrieve!(identifier)
308
- if uploader.class.version_names.blank?
309
- return RemoveableFile.new(identifier)
310
- else
311
- return nil # Version files should not be deleted.
312
- end
313
- end
314
-
315
- def identifier
316
- uploader.my_identifier
317
- end
121
+ end
318
122
  end
319
123
  end
@@ -0,0 +1,9 @@
1
+ module Cloudinary::CarrierWave
2
+ class UploadError < StandardError
3
+ attr_reader :http_code
4
+ def initialize(message, http_code)
5
+ super(message)
6
+ @http_code = http_code
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,62 @@
1
+ # Copyright Cloudinary
2
+ # Support for store in CarrierWave files that were preloaded to cloudinary (e.g., by javascript)
3
+ # Field value must be in the format: "image/upload/v<version>/#<public_id>.<format>#<signature>"
4
+ # Where signature is the cloduinary API signature on the public_id and version.
5
+ module Cloudinary::CarrierWave
6
+ PRELOADED_CLOUDINARY_PATH = /^([^\/]+)\/upload\/v(\d+)\/([^\/]+)#([^\/]+)$/
7
+
8
+ def cache!(new_file)
9
+ if new_file.is_a?(String) && new_file.match(PRELOADED_CLOUDINARY_PATH)
10
+ @file = PreloadedCloudinaryFile.new(new_file)
11
+ @stored_version = @file.version
12
+ @public_id = @stored_public_id = @file.public_id
13
+ self.original_filename = @file.original_filename
14
+ @cache_id = "unused" # must not be blank
15
+ else
16
+ super
17
+ @public_id = nil # allow overriding public_id
18
+ end
19
+ end
20
+
21
+ def retrieve_from_cache!(new_file)
22
+ if new_file.is_a?(String) && new_file.match(PRELOADED_CLOUDINARY_PATH)
23
+ @file = PreloadedCloudinaryFile.new(new_file)
24
+ @stored_version = @file.version
25
+ @public_id = @stored_public_id = @file.public_id
26
+ self.original_filename = @file.original_filename
27
+ @cache_id = "unused" # must not be blank
28
+ else
29
+ super
30
+ @public_id = nil # allow overriding public_id
31
+ end
32
+ end
33
+
34
+ def cache_name
35
+ return @file.is_a?(PreloadedCloudinaryFile) ? @file.to_s : super
36
+ end
37
+
38
+ class PreloadedCloudinaryFile
39
+ attr_reader :original_filename, :version, :public_id, :signature
40
+ def initialize(file_info)
41
+ resource_type, @version, @original_filename, @signature = file_info.scan(PRELOADED_CLOUDINARY_PATH).first
42
+ raise "Cloudinary CarrierWave integration supports images only" if resource_type != "image"
43
+ @public_id = @original_filename[0..(@original_filename.rindex(".")-1)]
44
+ expected_signature = Cloudinary::Utils.api_sign_request({:public_id=>public_id, :version=>version}, Cloudinary.config.api_secret)
45
+ if @signature != expected_signature
46
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.cloudinary_signature_error", :public_id=>public_id, :default=>"Invalid signature for #{public_id}")
47
+ end
48
+ end
49
+
50
+ def identifier
51
+ "v#{version}/#{original_filename}"
52
+ end
53
+
54
+ def to_s
55
+ "image/upload/v#{version}/#{original_filename}##{signature}"
56
+ end
57
+
58
+ def delete
59
+ # Do nothing. This is a virtual file.
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,117 @@
1
+ module Cloudinary::CarrierWave
2
+ module ClassMethods
3
+ def eager
4
+ process :eager => true
5
+ end
6
+
7
+ def convert(format)
8
+ process :convert => format
9
+ end
10
+
11
+ def resize_to_limit(width, height)
12
+ process :resize_to_limit => [width, height]
13
+ end
14
+
15
+ def resize_to_fit(width, height)
16
+ process :resize_to_fit => [width, height]
17
+ end
18
+
19
+ def resize_to_fill(width, height, gravity="Center")
20
+ process :resize_to_fill => [width, height, gravity]
21
+ end
22
+
23
+ def resize_and_pad(width, height, background=:transparent, gravity="Center")
24
+ process :resize_and_pad => [width, height, background, gravity]
25
+ end
26
+
27
+ def scale(width, height)
28
+ process :scale => [width, height]
29
+ end
30
+
31
+ def crop(width, height, gravity="Center")
32
+ process :crop => [width, height, gravity]
33
+ end
34
+
35
+ def cloudinary_transformation(options)
36
+ process :cloudinary_transformation => options
37
+ end
38
+
39
+ def tags(*tags)
40
+ process :tags=>tags
41
+ end
42
+ end
43
+
44
+ def set_or_yell(hash, attr, value)
45
+ raise "conflicting transformation on #{attr} #{value}!=#{hash[attr]}" if hash[attr]
46
+ hash[attr] = value
47
+ end
48
+
49
+ def transformation
50
+ return @transformation if @transformation
51
+ @transformation = {}
52
+ self.class.processors.each do
53
+ |name, args|
54
+ case name
55
+ when :convert # Do nothing. This is handled by format
56
+ when :resize_to_limit
57
+ set_or_yell(@transformation, :width, args[0])
58
+ set_or_yell(@transformation, :height, args[1])
59
+ set_or_yell(@transformation, :crop, :limit)
60
+ when :resize_to_fit
61
+ set_or_yell(@transformation, :width, args[0])
62
+ set_or_yell(@transformation, :height, args[1])
63
+ set_or_yell(@transformation, :crop, :fit)
64
+ when :resize_to_fill
65
+ set_or_yell(@transformation, :width, args[0])
66
+ set_or_yell(@transformation, :height, args[1])
67
+ set_or_yell(@transformation, :gravity, args[2].to_s.downcase)
68
+ set_or_yell(@transformation, :crop, :fill)
69
+ when :resize_and_pad
70
+ set_or_yell(@transformation, :width, args[0])
71
+ set_or_yell(@transformation, :height, args[1])
72
+ set_or_yell(@transformation, :background, args[2].to_s.downcase)
73
+ set_or_yell(@transformation, :gravity, args[3].to_s.downcase)
74
+ set_or_yell(@transformation, :crop, :pad)
75
+ when :scale
76
+ set_or_yell(@transformation, :width, args[0])
77
+ set_or_yell(@transformation, :height, args[1])
78
+ set_or_yell(@transformation, :crop, :scale)
79
+ when :crop
80
+ set_or_yell(@transformation, :width, args[0])
81
+ set_or_yell(@transformation, :height, args[1])
82
+ set_or_yell(@transformation, :gravity, args[2].to_s.downcase)
83
+ set_or_yell(@transformation, :crop, :crop)
84
+ when :cloudinary_transformation
85
+ args.each do
86
+ |attr, value|
87
+ set_or_yell(@transformation, attr, value)
88
+ end
89
+ end
90
+ end
91
+ @transformation
92
+ end
93
+
94
+ def eager
95
+ @eager ||= self.class.processors.any?{|processor| processor[0] == :eager}
96
+ end
97
+
98
+ def tags
99
+ @tags ||= self.class.processors.select{|processor| processor[0] == :tags}.map(&:last).first
100
+ end
101
+
102
+ def format
103
+ format_processor = self.class.processors.find{|processor| processor[0] == :convert}
104
+ if format_processor
105
+ # Explicit format is given
106
+ return Array(format_processor[1]).first
107
+ elsif self.version_name.present?
108
+ # No local format. The reset should be handled by main uploader
109
+ uploader = self.model.send(self.mounted_as)
110
+ return uploader.format
111
+ else
112
+ # Try to auto-detect format
113
+ format = Cloudinary::CarrierWave.split_format(original_filename || "").last
114
+ return format || "png" # TODO Default format?
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,23 @@
1
+ module Cloudinary::CarrierWave
2
+ def download!(uri)
3
+ if respond_to?(:process_uri)
4
+ uri = process_uri(uri)
5
+ else # Backward compatibility with old CarrierWave
6
+ uri = URI.parse(URI.escape(URI.unescape(uri)))
7
+ end
8
+ self.original_filename = @cache_id = @filename = File.basename(uri.path).gsub(/[^a-zA-Z0-9\.\-\+_]/, '')
9
+ @file = RemoteFile.new(uri, @filename)
10
+ end
11
+
12
+ class RemoteFile
13
+ attr_reader :uri, :original_filename
14
+ def initialize(uri, filename)
15
+ @uri = uri
16
+ @original_filename = filename
17
+ end
18
+
19
+ def delete
20
+ # Do nothing. This is a virtual file.
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,51 @@
1
+ class Cloudinary::CarrierWave::Storage < ::CarrierWave::Storage::Abstract
2
+ def store!(file)
3
+ if uploader.class.version_names.blank?
4
+ return store_cloudinary_version(file.version) if file.is_a?(Cloudinary::CarrierWave::PreloadedCloudinaryFile)
5
+
6
+ # This is the toplevel, need to upload the actual file.
7
+ params = uploader.transformation.dup
8
+ params[:return_error] = true
9
+ params[:format] = uploader.format
10
+ params[:public_id] = uploader.my_public_id
11
+ params[:tags] = uploader.tags if uploader.tags
12
+ eager_versions = uploader.versions.values.select(&:eager)
13
+ params[:eager] = eager_versions.map{|version| [version.transformation, version.format]} if eager_versions.length > 0
14
+
15
+ data = nil
16
+ if (file.is_a?(Cloudinary::CarrierWave::RemoteFile))
17
+ data = file.uri.to_s
18
+ else
19
+ data = file.file
20
+ data.rewind if !file.is_path? && data.respond_to?(:rewind)
21
+ end
22
+ uploader.metadata = Cloudinary::Uploader.upload(data, params)
23
+ if uploader.metadata["error"]
24
+ raise Cloudinary::CarrierWave::UploadError.new(uploader.metadata["error"]["message"], uploader.metadata["error"]["http_code"])
25
+ end
26
+
27
+ store_cloudinary_version(uploader.metadata["version"]) if uploader.metadata["version"]
28
+ # Will throw an exception on error
29
+ else
30
+ raise "nested versions are not allowed." if (uploader.class.version_names.length > 1)
31
+ # Do nothing - versions are not handled locally.
32
+ end
33
+ nil
34
+ end
35
+
36
+ def store_cloudinary_version(version)
37
+ name = "v#{version}/#{identifier.split("/").last}"
38
+ model_class = uploader.model.class
39
+ if defined?(ActiveRecord::Base) && uploader.model.is_a?(ActiveRecord::Base)
40
+ primary_key = model_class.primary_key.to_sym
41
+ model_class.update_all({uploader.mounted_as=>name}, {primary_key=>uploader.model.send(primary_key)})
42
+ uploader.model.send :write_attribute, uploader.mounted_as, name
43
+ elsif model_class.respond_to?(:update_all) && uploader.model.respond_to?(:_id)
44
+ # Mongoid support
45
+ model_class.where(:_id=>uploader.model._id).update_all(uploader.mounted_as=>name)
46
+ uploader.model.send :write_attribute, uploader.mounted_as, name
47
+ else
48
+ raise "Only ActiveRecord and Mongoid are supported at the moment!"
49
+ end
50
+ end
51
+ end
@@ -20,7 +20,7 @@ module CloudinaryHelper
20
20
  def cl_image_path(source, options = {})
21
21
  options = options.clone
22
22
  url = cloudinary_url(source, options)
23
- original_image_path(url, options)
23
+ original_image_path(url)
24
24
  end
25
25
 
26
26
  def image_tag(*args)
@@ -188,6 +188,7 @@ rescue LoadError
188
188
  end
189
189
 
190
190
  begin
191
+ require 'sass'
191
192
  require 'sass/script/functions'
192
193
  module Sass::Script::Functions
193
194
  def cloudinary_url(public_id, sass_options={})
@@ -21,6 +21,8 @@ class Cloudinary::Utils
21
21
  y = options.delete(:y)
22
22
  radius = options.delete(:radius)
23
23
  default_image = options.delete(:default_image)
24
+ background = options.delete(:background)
25
+ background = background.sub(/^#/, 'rgb:') if background
24
26
 
25
27
  gravity = options.delete(:gravity)
26
28
  quality = options.delete(:quality)
@@ -36,7 +38,7 @@ class Cloudinary::Utils
36
38
  end
37
39
  prefix = options.delete(:prefix)
38
40
 
39
- params = {:w=>width, :h=>height, :t=>named_transformation, :c=>crop, :q=>quality, :g=>gravity, :p=>prefix, :x=>x, :y=>y, :r=>radius, :d=>default_image}
41
+ params = {:w=>width, :h=>height, :t=>named_transformation, :c=>crop, :q=>quality, :g=>gravity, :p=>prefix, :x=>x, :y=>y, :r=>radius, :d=>default_image, :b=>background}
40
42
  transformation = params.reject{|k,v| v.blank?}.map{|k,v| [k.to_s, v]}.sort_by(&:first).map{|k,v| "#{k}_#{v}"}.join(",")
41
43
  raw_transformation = options.delete(:raw_transformation)
42
44
  transformation = [transformation, raw_transformation].reject(&:blank?).join(",")
@@ -64,6 +66,7 @@ class Cloudinary::Utils
64
66
  secure_distribution = options.delete(:secure_distribution) || Cloudinary.config.secure_distribution
65
67
  force_remote = options.delete(:force_remote)
66
68
 
69
+ return original_source if source.blank?
67
70
  if !force_remote
68
71
  return original_source if (type.nil? || type == :asset) && source.match(%r(^https?:/)i)
69
72
  if source.start_with?("/")
@@ -138,6 +141,10 @@ class Cloudinary::Utils
138
141
  def self.random_public_id
139
142
  (defined?(ActiveSupport::SecureRandom) ? ActiveSupport::SecureRandom : SecureRandom).base64(16).downcase.gsub(/[^a-z0-9]/, "")
140
143
  end
144
+
145
+ def self.signed_preloaded_image(result)
146
+ "#{result["resource_type"]}/upload/v#{result["version"]}/#{[result["public_id"], result["format"]].join(".")}##{result["signature"]}"
147
+ end
141
148
 
142
149
  @@json_decode = false
143
150
  def self.json_decode(str)
@@ -1,4 +1,4 @@
1
1
  # Copyright Cloudinary
2
2
  module Cloudinary
3
- VERSION = "1.0.15"
3
+ VERSION = "1.0.16"
4
4
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: cloudinary
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 1.0.15
5
+ version: 1.0.16
6
6
  platform: ruby
7
7
  authors:
8
8
  - Nadav Soferman
@@ -12,7 +12,7 @@ autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
14
 
15
- date: 2012-04-23 00:00:00 +03:00
15
+ date: 2012-04-25 00:00:00 +03:00
16
16
  default_executable:
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
@@ -58,6 +58,11 @@ files:
58
58
  - lib/cloudinary.rb
59
59
  - lib/cloudinary/blob.rb
60
60
  - lib/cloudinary/carrier_wave.rb
61
+ - lib/cloudinary/carrier_wave/error.rb
62
+ - lib/cloudinary/carrier_wave/preloaded.rb
63
+ - lib/cloudinary/carrier_wave/process.rb
64
+ - lib/cloudinary/carrier_wave/remote.rb
65
+ - lib/cloudinary/carrier_wave/storage.rb
61
66
  - lib/cloudinary/controller.rb
62
67
  - lib/cloudinary/downloader.rb
63
68
  - lib/cloudinary/helper.rb