cloudinary 1.4.0 → 1.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +13 -5
- data/CHANGELOG.md +31 -0
- data/cloudinary.gemspec +6 -3
- data/lib/cloudinary.rb +67 -39
- data/lib/cloudinary/api.rb +46 -0
- data/lib/cloudinary/auth_token.rb +68 -0
- data/lib/cloudinary/carrier_wave/remote.rb +3 -3
- data/lib/cloudinary/ostruct2.rb +284 -0
- data/lib/cloudinary/uploader.rb +1 -0
- data/lib/cloudinary/utils.rb +35 -15
- data/lib/cloudinary/version.rb +1 -1
- data/spec/api_spec.rb +63 -21
- data/spec/archive_spec.rb +1 -1
- data/spec/auth_token_spec.rb +82 -0
- data/spec/cloudinary_helper_spec.rb +16 -0
- data/spec/cloudinary_spec.rb +15 -0
- data/spec/utils_methods_spec.rb +0 -33
- metadata +65 -34
- data/lib/cloudinary/akamai.rb +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
M2E1MWI3ZmRiNDA2YWE2ZTA0OTYzZDUyZmIxMmNjYWJhMjFhNzVmNA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NjUzMWYwOGVkNTU0ZmE3ZDIyZjJjNzM1MGE5YWM4ZWRhYWY2OGQyYg==
|
5
7
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NDFjMjgyNTE1MTkxODkxZjcyN2VmOGU3ODZiMWU2NjAyMWNiODc2YjJiMGMz
|
10
|
+
YzkxOTYyZjRkOTU4MmM2MGMxNThmMDQ3NThiMWUxOTFmZGE0OTBkNjJkYTlk
|
11
|
+
ZWI1MDdhOGVjMzUyZmE2NzMzMWQ3M2U0MTFmZWY5NmYxNDJjNWY=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZDEzODU4YWJhOGExNmQxZjY2NGI5OTZkMjk1NmFiMGY1YWFiZTdkODcwZWIw
|
14
|
+
MmM5NGQ5OWIzZGJmNjBhZmE1OGViZmFhZGMzOGJmNTU4M2M2ZTAzM2FkNTlk
|
15
|
+
ZDMxNDA1MDNmOGE4ODk5M2Y5MDkyZDRhOTI4NTNlOGVhYmRjZWI=
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,35 @@
|
|
1
1
|
|
2
|
+
1.5.2 / 2017-02-22
|
3
|
+
==================
|
4
|
+
|
5
|
+
* Support URL Authorization token.
|
6
|
+
* Rename auth_token.
|
7
|
+
* Support nested keys in CLOUDINARY_URL
|
8
|
+
* Support "authenticated" url without a signature.
|
9
|
+
* Add OpenStruct from ruby 2.0.
|
10
|
+
* Add specific rubyzip version for ruby 1.9
|
11
|
+
|
12
|
+
1.5.1 / 2017-02-13
|
13
|
+
==================
|
14
|
+
* Fix Carrierwave 1.0.0 integration: broken `remote_image_url`
|
15
|
+
|
16
|
+
1.5.0 / 2017-02-07
|
17
|
+
==================
|
18
|
+
|
19
|
+
New functionality and features
|
20
|
+
------------------------------
|
21
|
+
|
22
|
+
* Access mode API
|
23
|
+
|
24
|
+
Other Changes
|
25
|
+
-------------
|
26
|
+
|
27
|
+
* Fix transformation related tests.
|
28
|
+
* Fix archive test to use `include` instead of `match_array`.
|
29
|
+
* Fix "missing folder" test
|
30
|
+
* Add specific dependency on nokogiri
|
31
|
+
* Update rspec version
|
32
|
+
|
2
33
|
1.4.0 / 2017-01-30
|
3
34
|
==================
|
4
35
|
|
data/cloudinary.gemspec
CHANGED
@@ -21,23 +21,26 @@ Gem::Specification.new do |s|
|
|
21
21
|
s.require_paths = ["lib"]
|
22
22
|
|
23
23
|
s.add_dependency "aws_cf_signer"
|
24
|
-
s.add_development_dependency "rspec", '>=3.
|
24
|
+
s.add_development_dependency "rspec", '>=3.5'
|
25
25
|
s.add_development_dependency "rspec-rails"
|
26
|
-
s.add_development_dependency "rubyzip"
|
27
26
|
|
28
27
|
if RUBY_VERSION > "2.0"
|
29
28
|
s.add_dependency "rest-client"
|
30
29
|
s.add_development_dependency "actionpack"
|
31
30
|
s.add_development_dependency "simplecov"
|
32
|
-
|
31
|
+
s.add_development_dependency "rubyzip"
|
32
|
+
elsif RUBY_VERSION >= "1.9"
|
33
33
|
s.add_dependency "rest-client", '< 2.0'
|
34
34
|
s.add_dependency 'json', '~> 1.8'
|
35
35
|
s.add_development_dependency "actionpack", '< 5.0'
|
36
36
|
s.add_development_dependency "simplecov"
|
37
|
+
s.add_development_dependency "nokogiri", "<1.7.0"
|
38
|
+
s.add_development_dependency "rubyzip", '<1.2.1'
|
37
39
|
else
|
38
40
|
s.add_dependency "i18n", "<0.7.0"
|
39
41
|
s.add_dependency "rest-client", "<=1.6.8"
|
40
42
|
s.add_development_dependency "actionpack", "~>3.2.0"
|
43
|
+
s.add_development_dependency "nokogiri", "<1.6.0"
|
41
44
|
end
|
42
45
|
|
43
46
|
end
|
data/lib/cloudinary.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# Copyright Cloudinary
|
2
|
-
|
2
|
+
if RUBY_VERSION > "2"
|
3
|
+
require "ostruct"
|
4
|
+
else
|
5
|
+
require "cloudinary/ostruct2"
|
6
|
+
end
|
7
|
+
|
3
8
|
require "pathname"
|
4
9
|
require "yaml"
|
5
10
|
require "uri"
|
@@ -15,15 +20,15 @@ module Cloudinary
|
|
15
20
|
autoload :Downloader, "cloudinary/downloader"
|
16
21
|
autoload :Blob, "cloudinary/blob"
|
17
22
|
autoload :PreloadedFile, "cloudinary/preloaded_file"
|
18
|
-
autoload :Static, "cloudinary/static"
|
19
|
-
autoload :CarrierWave, "cloudinary/carrier_wave"
|
20
|
-
|
21
|
-
CF_SHARED_CDN
|
22
|
-
AKAMAI_SHARED_CDN
|
23
|
+
autoload :Static, "cloudinary/static"
|
24
|
+
autoload :CarrierWave, "cloudinary/carrier_wave"
|
25
|
+
|
26
|
+
CF_SHARED_CDN = "d3jpl91pxevbkh.cloudfront.net"
|
27
|
+
AKAMAI_SHARED_CDN = "res.cloudinary.com"
|
23
28
|
OLD_AKAMAI_SHARED_CDN = "cloudinary-a.akamaihd.net"
|
24
|
-
SHARED_CDN
|
25
|
-
|
26
|
-
USER_AGENT
|
29
|
+
SHARED_CDN = AKAMAI_SHARED_CDN
|
30
|
+
|
31
|
+
USER_AGENT = "CloudinaryRuby/" + VERSION
|
27
32
|
@@user_platform = ""
|
28
33
|
|
29
34
|
# Add platform information to the USER_AGENT header
|
@@ -46,27 +51,27 @@ module Cloudinary
|
|
46
51
|
|
47
52
|
FORMAT_ALIASES = {
|
48
53
|
"jpeg" => "jpg",
|
49
|
-
"jpe"
|
50
|
-
"tif"
|
51
|
-
"ps"
|
52
|
-
"ept"
|
54
|
+
"jpe" => "jpg",
|
55
|
+
"tif" => "tiff",
|
56
|
+
"ps" => "eps",
|
57
|
+
"ept" => "eps"
|
53
58
|
}
|
54
|
-
|
59
|
+
|
55
60
|
@@config = nil
|
56
|
-
|
61
|
+
|
57
62
|
def self.config(new_config=nil)
|
58
63
|
first_time = @@config.nil?
|
59
|
-
@@config
|
60
|
-
|
64
|
+
@@config ||= OpenStruct.new((YAML.load(ERB.new(IO.read(config_dir.join("cloudinary.yml"))).result)[config_env] rescue {}))
|
65
|
+
|
61
66
|
# Heroku support
|
62
67
|
if first_time && ENV["CLOUDINARY_CLOUD_NAME"]
|
63
68
|
set_config(
|
64
|
-
"cloud_name"
|
65
|
-
"api_key"
|
66
|
-
"api_secret"
|
69
|
+
"cloud_name" => ENV["CLOUDINARY_CLOUD_NAME"],
|
70
|
+
"api_key" => ENV["CLOUDINARY_API_KEY"],
|
71
|
+
"api_secret" => ENV["CLOUDINARY_API_SECRET"],
|
67
72
|
"secure_distribution" => ENV["CLOUDINARY_SECURE_DISTRIBUTION"],
|
68
|
-
"private_cdn"
|
69
|
-
"secure"
|
73
|
+
"private_cdn" => ENV["CLOUDINARY_PRIVATE_CDN"].to_s == 'true',
|
74
|
+
"secure" => ENV["CLOUDINARY_SECURE"].to_s == 'true'
|
70
75
|
)
|
71
76
|
elsif first_time && ENV["CLOUDINARY_URL"]
|
72
77
|
config_from_url(ENV["CLOUDINARY_URL"])
|
@@ -75,26 +80,50 @@ module Cloudinary
|
|
75
80
|
set_config(new_config) if new_config
|
76
81
|
yield(@@config) if block_given?
|
77
82
|
|
78
|
-
@@config
|
83
|
+
@@config
|
79
84
|
end
|
80
|
-
|
85
|
+
|
81
86
|
def self.config_from_url(url)
|
82
87
|
@@config ||= OpenStruct.new
|
83
|
-
uri
|
88
|
+
uri = URI.parse(url)
|
84
89
|
set_config(
|
85
|
-
"cloud_name"
|
86
|
-
"api_key"
|
87
|
-
"api_secret"
|
88
|
-
"private_cdn"
|
90
|
+
"cloud_name" => uri.host,
|
91
|
+
"api_key" => uri.user,
|
92
|
+
"api_secret" => uri.password,
|
93
|
+
"private_cdn" => !uri.path.blank?,
|
89
94
|
"secure_distribution" => uri.path[1..-1]
|
90
95
|
)
|
91
96
|
uri.query.to_s.split("&").each do
|
92
|
-
|
97
|
+
|param|
|
93
98
|
key, value = param.split("=")
|
94
|
-
|
95
|
-
|
99
|
+
if isNestedKey? key
|
100
|
+
putNestedKey key, value
|
101
|
+
else
|
102
|
+
set_config(key => URI.decode(value))
|
103
|
+
end
|
104
|
+
end
|
96
105
|
end
|
97
|
-
|
106
|
+
|
107
|
+
def self.putNestedKey(key, value)
|
108
|
+
chain = key.split(/[\[\]]+/).reject { |i| i.empty? }
|
109
|
+
outer = @@config
|
110
|
+
lastKey = chain.pop()
|
111
|
+
chain.each do |innerKey|
|
112
|
+
inner = outer[innerKey]
|
113
|
+
if inner.nil?
|
114
|
+
inner = OpenStruct.new
|
115
|
+
outer[innerKey] = inner
|
116
|
+
end
|
117
|
+
outer = inner
|
118
|
+
end
|
119
|
+
outer[lastKey] = value
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
def self.isNestedKey?(key)
|
124
|
+
/\w+\[\w+\]/ =~ key
|
125
|
+
end
|
126
|
+
|
98
127
|
def self.app_root
|
99
128
|
if defined? Rails::root
|
100
129
|
# Rails 2.2 return String for Rails.root
|
@@ -121,10 +150,9 @@ module Cloudinary
|
|
121
150
|
new_config.each{|k,v| @@config.send(:"#{k}=", v) if !v.nil?}
|
122
151
|
end
|
123
152
|
end
|
124
|
-
|
125
|
-
|
126
|
-
require "cloudinary/
|
127
|
-
require "cloudinary/
|
128
|
-
require "cloudinary/
|
129
|
-
require "cloudinary/engine" if defined?(Rails) && defined?(Rails::Engine)
|
153
|
+
# Prevent require loop if included after Rails is already initialized.
|
154
|
+
require "cloudinary/helper" if defined?(::ActionView::Base)
|
155
|
+
require "cloudinary/controller" if defined?(::ActionController::Base)
|
156
|
+
require "cloudinary/railtie" if defined?(Rails) && defined?(Rails::Railtie)
|
157
|
+
require "cloudinary/engine" if defined?(Rails) && defined?(Rails::Engine)
|
130
158
|
|
data/lib/cloudinary/api.rb
CHANGED
@@ -267,6 +267,42 @@ class Cloudinary::Api
|
|
267
267
|
call_api(:put, "streaming_profiles/#{name}", params, options)
|
268
268
|
end
|
269
269
|
|
270
|
+
# Update resources access mode. Resources are selected by the prefix
|
271
|
+
# @param [String] access_mode the access mode to set the resources to
|
272
|
+
# @param [String] prefix The prefix by which to filter applicable resources
|
273
|
+
# @param [Object] options additional options
|
274
|
+
# @option options [String] :resource_type ("image") the type of resources to modify
|
275
|
+
# @option options [Fixnum] :max_results (nil) the maximum resources to process in a single invocation
|
276
|
+
# @option options [String] :next_cursor (nil) provided by a previous call to the method
|
277
|
+
def self.update_resources_access_mode_by_prefix(access_mode, prefix, options = {})
|
278
|
+
|
279
|
+
update_resources_access_mode(access_mode, :prefix, prefix, options)
|
280
|
+
end
|
281
|
+
|
282
|
+
# Update resources access mode. Resources are selected by the tag
|
283
|
+
# @param [String] access_mode the access mode to set the resources to
|
284
|
+
# @param [String] tag the tag by which to filter applicable resources
|
285
|
+
# @param [Object] options additional options
|
286
|
+
# @option options [String] :resource_type ("image") the type of resources to modify
|
287
|
+
# @option options [Fixnum] :max_results (nil) the maximum resources to process in a single invocation
|
288
|
+
# @option options [String] :next_cursor (nil) provided by a previous call to the method
|
289
|
+
def self.update_resources_access_mode_by_tag(access_mode, tag, options = {})
|
290
|
+
|
291
|
+
update_resources_access_mode(access_mode, :tag, tag, options)
|
292
|
+
end
|
293
|
+
|
294
|
+
# Update resources access mode. Resources are selected by the provided public_ids
|
295
|
+
# @param [String] access_mode the access mode to set the resources to
|
296
|
+
# @param [Array<String>] public_ids The prefix by which to filter applicable resources
|
297
|
+
# @param [Object] options additional options
|
298
|
+
# @option options [String] :resource_type ("image") the type of resources to modify
|
299
|
+
# @option options [Fixnum] :max_results (nil) the maximum resources to process in a single invocation
|
300
|
+
# @option options [String] :next_cursor (nil) provided by a previous call to the method
|
301
|
+
def self.update_resources_access_mode_by_ids(access_mode, public_ids, options = {})
|
302
|
+
|
303
|
+
update_resources_access_mode(access_mode, :public_ids, public_ids, options)
|
304
|
+
end
|
305
|
+
|
270
306
|
protected
|
271
307
|
|
272
308
|
def self.call_api(method, uri, params, options)
|
@@ -320,4 +356,14 @@ class Cloudinary::Api
|
|
320
356
|
def self.transformation_string(transformation)
|
321
357
|
transformation.is_a?(String) ? transformation : Cloudinary::Utils.generate_transformation_string(transformation.clone)
|
322
358
|
end
|
359
|
+
|
360
|
+
def self.update_resources_access_mode(access_mode, by_key, value, options = {})
|
361
|
+
resource_type = options[:resource_type] || "image"
|
362
|
+
type = options[:type] || "upload"
|
363
|
+
params = only(options, :next_cursor)
|
364
|
+
params[:access_mode] = access_mode
|
365
|
+
params[by_key] = value
|
366
|
+
call_api("post", "resources/#{resource_type}/#{type}/update_access_mode", params, options)
|
367
|
+
end
|
368
|
+
|
323
369
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
if RUBY_VERSION > "2"
|
3
|
+
require "ostruct"
|
4
|
+
else
|
5
|
+
require "cloudinary/ostruct2"
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
module Cloudinary
|
10
|
+
module AuthToken
|
11
|
+
SEPARATOR = '~'
|
12
|
+
|
13
|
+
def self.generate(options = {})
|
14
|
+
key = options[:key]
|
15
|
+
raise "Missing auth token key configuration" unless key
|
16
|
+
name = options[:token_name] || "__cld_token__"
|
17
|
+
start = options[:start_time]
|
18
|
+
expiration = options[:expiration]
|
19
|
+
ip = options[:ip]
|
20
|
+
acl = options[:acl]
|
21
|
+
duration = options[:duration]
|
22
|
+
url = options[:url]
|
23
|
+
start = Time.new.getgm.to_i if start == 'now'
|
24
|
+
if expiration.nil? || expiration == 0
|
25
|
+
if !(duration.nil? || duration == 0)
|
26
|
+
expiration = (start || Time.new.getgm.to_i) + duration
|
27
|
+
else
|
28
|
+
raise 'Must provide either expiration or duration'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
token = []
|
33
|
+
token << "ip=#{ip}" if ip
|
34
|
+
token << "st=#{start}" if start
|
35
|
+
token << "exp=#{expiration}"
|
36
|
+
token << "acl=#{escape_to_lower(acl)}" if acl
|
37
|
+
to_sign = token.clone
|
38
|
+
to_sign << "url=#{escape_to_lower(url)}" if url
|
39
|
+
auth = digest(to_sign.join(SEPARATOR), key)
|
40
|
+
token << "hmac=#{auth}"
|
41
|
+
"#{name}=#{token.join(SEPARATOR)}"
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
# Merge token2 to token1 returning a new
|
46
|
+
# Requires to support Ruby 1.9
|
47
|
+
def self.merge_auth_token(token1, token2)
|
48
|
+
token1 = token1 || {}
|
49
|
+
token2 = token2 || {}
|
50
|
+
token1 = token1.respond_to?( :to_h) ? token1.to_h : token1
|
51
|
+
token2 = token2.respond_to?( :to_h) ? token2.to_h : token2
|
52
|
+
token1.merge(token2)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# escape URI pattern using lowercase hex. For example "/" -> "%2f".
|
58
|
+
def self.escape_to_lower(url)
|
59
|
+
CGI::escape(url).gsub(/%../) { |h| h.downcase }
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.digest(message, key)
|
63
|
+
bin_key = Array(key).pack("H*")
|
64
|
+
digest = OpenSSL::Digest::SHA256.new
|
65
|
+
OpenSSL::HMAC.hexdigest(digest, bin_key, message)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Cloudinary::CarrierWave
|
2
|
-
def download!(uri)
|
2
|
+
def download!(uri, *args)
|
3
3
|
return super if !self.cloudinary_should_handle_remote?
|
4
4
|
if respond_to?(:process_uri)
|
5
5
|
uri = process_uri(uri)
|
@@ -17,9 +17,9 @@ module Cloudinary::CarrierWave
|
|
17
17
|
@uri = uri
|
18
18
|
@original_filename = filename
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
def delete
|
22
22
|
# Do nothing. This is a virtual file.
|
23
23
|
end
|
24
24
|
end
|
25
|
-
end
|
25
|
+
end
|
@@ -0,0 +1,284 @@
|
|
1
|
+
#
|
2
|
+
# = ostruct.rb: OpenStruct implementation
|
3
|
+
#
|
4
|
+
# Author:: Yukihiro Matsumoto
|
5
|
+
# Documentation:: Gavin Sinclair
|
6
|
+
#
|
7
|
+
# OpenStruct allows the creation of data objects with arbitrary attributes.
|
8
|
+
# See OpenStruct for an example.
|
9
|
+
#
|
10
|
+
|
11
|
+
#
|
12
|
+
# An OpenStruct is a data structure, similar to a Hash, that allows the
|
13
|
+
# definition of arbitrary attributes with their accompanying values. This is
|
14
|
+
# accomplished by using Ruby's metaprogramming to define methods on the class
|
15
|
+
# itself.
|
16
|
+
#
|
17
|
+
# == Examples:
|
18
|
+
#
|
19
|
+
# require 'ostruct'
|
20
|
+
#
|
21
|
+
# person = OpenStruct.new
|
22
|
+
# person.name = "John Smith"
|
23
|
+
# person.age = 70
|
24
|
+
# person.pension = 300
|
25
|
+
#
|
26
|
+
# puts person.name # -> "John Smith"
|
27
|
+
# puts person.age # -> 70
|
28
|
+
# puts person.address # -> nil
|
29
|
+
#
|
30
|
+
# An OpenStruct employs a Hash internally to store the methods and values and
|
31
|
+
# can even be initialized with one:
|
32
|
+
#
|
33
|
+
# australia = OpenStruct.new(:country => "Australia", :population => 20_000_000)
|
34
|
+
# p australia # -> <OpenStruct country="Australia" population=20000000>
|
35
|
+
#
|
36
|
+
# Hash keys with spaces or characters that would normally not be able to use for
|
37
|
+
# method calls (e.g. ()[]*) will not be immediately available on the
|
38
|
+
# OpenStruct object as a method for retrieval or assignment, but can be still be
|
39
|
+
# reached through the Object#send method.
|
40
|
+
#
|
41
|
+
# measurements = OpenStruct.new("length (in inches)" => 24)
|
42
|
+
# measurements.send("length (in inches)") # -> 24
|
43
|
+
#
|
44
|
+
# data_point = OpenStruct.new(:queued? => true)
|
45
|
+
# data_point.queued? # -> true
|
46
|
+
# data_point.send("queued?=",false)
|
47
|
+
# data_point.queued? # -> false
|
48
|
+
#
|
49
|
+
# Removing the presence of a method requires the execution the delete_field
|
50
|
+
# method as setting the property value to +nil+ will not remove the method.
|
51
|
+
#
|
52
|
+
# first_pet = OpenStruct.new(:name => 'Rowdy', :owner => 'John Smith')
|
53
|
+
# first_pet.owner = nil
|
54
|
+
# second_pet = OpenStruct.new(:name => 'Rowdy')
|
55
|
+
#
|
56
|
+
# first_pet == second_pet # -> false
|
57
|
+
#
|
58
|
+
# first_pet.delete_field(:owner)
|
59
|
+
# first_pet == second_pet # -> true
|
60
|
+
#
|
61
|
+
#
|
62
|
+
# == Implementation:
|
63
|
+
#
|
64
|
+
# An OpenStruct utilizes Ruby's method lookup structure to and find and define
|
65
|
+
# the necessary methods for properties. This is accomplished through the method
|
66
|
+
# method_missing and define_method.
|
67
|
+
#
|
68
|
+
# This should be a consideration if there is a concern about the performance of
|
69
|
+
# the objects that are created, as there is much more overhead in the setting
|
70
|
+
# of these properties compared to using a Hash or a Struct.
|
71
|
+
#
|
72
|
+
class OpenStruct
|
73
|
+
#
|
74
|
+
# Creates a new OpenStruct object. By default, the resulting OpenStruct
|
75
|
+
# object will have no attributes.
|
76
|
+
#
|
77
|
+
# The optional +hash+, if given, will generate attributes and values
|
78
|
+
# (can be a Hash, an OpenStruct or a Struct).
|
79
|
+
# For example:
|
80
|
+
#
|
81
|
+
# require 'ostruct'
|
82
|
+
# hash = { "country" => "Australia", :population => 20_000_000 }
|
83
|
+
# data = OpenStruct.new(hash)
|
84
|
+
#
|
85
|
+
# p data # -> <OpenStruct country="Australia" population=20000000>
|
86
|
+
#
|
87
|
+
def initialize(hash=nil)
|
88
|
+
@table = {}
|
89
|
+
if hash
|
90
|
+
hash.each_pair do |k, v|
|
91
|
+
k = k.to_sym
|
92
|
+
@table[k] = v
|
93
|
+
new_ostruct_member(k)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Duplicate an OpenStruct object members.
|
99
|
+
def initialize_copy(orig)
|
100
|
+
super
|
101
|
+
@table = @table.dup
|
102
|
+
@table.each_key{|key| new_ostruct_member(key)}
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Converts the OpenStruct to a hash with keys representing
|
107
|
+
# each attribute (as symbols) and their corresponding values
|
108
|
+
# Example:
|
109
|
+
#
|
110
|
+
# require 'ostruct'
|
111
|
+
# data = OpenStruct.new("country" => "Australia", :population => 20_000_000)
|
112
|
+
# data.to_h # => {:country => "Australia", :population => 20000000 }
|
113
|
+
#
|
114
|
+
def to_h
|
115
|
+
@table.dup
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
# Yields all attributes (as a symbol) along with the corresponding values
|
120
|
+
# or returns an enumerator if not block is given.
|
121
|
+
# Example:
|
122
|
+
#
|
123
|
+
# require 'ostruct'
|
124
|
+
# data = OpenStruct.new("country" => "Australia", :population => 20_000_000)
|
125
|
+
# data.each_pair.to_a # => [[:country, "Australia"], [:population, 20000000]]
|
126
|
+
#
|
127
|
+
def each_pair
|
128
|
+
return to_enum __method__ unless block_given?
|
129
|
+
@table.each_pair{|p| yield p}
|
130
|
+
end
|
131
|
+
|
132
|
+
#
|
133
|
+
# Provides marshalling support for use by the Marshal library.
|
134
|
+
#
|
135
|
+
def marshal_dump
|
136
|
+
@table
|
137
|
+
end
|
138
|
+
|
139
|
+
#
|
140
|
+
# Provides marshalling support for use by the Marshal library.
|
141
|
+
#
|
142
|
+
def marshal_load(x)
|
143
|
+
@table = x
|
144
|
+
@table.each_key{|key| new_ostruct_member(key)}
|
145
|
+
end
|
146
|
+
|
147
|
+
#
|
148
|
+
# Used internally to check if the OpenStruct is able to be
|
149
|
+
# modified before granting access to the internal Hash table to be modified.
|
150
|
+
#
|
151
|
+
def modifiable
|
152
|
+
begin
|
153
|
+
@modifiable = true
|
154
|
+
rescue
|
155
|
+
raise TypeError, "can't modify frozen #{self.class}", caller(3)
|
156
|
+
end
|
157
|
+
@table
|
158
|
+
end
|
159
|
+
protected :modifiable
|
160
|
+
|
161
|
+
#
|
162
|
+
# Used internally to defined properties on the
|
163
|
+
# OpenStruct. It does this by using the metaprogramming function
|
164
|
+
# define_singleton_method for both the getter method and the setter method.
|
165
|
+
#
|
166
|
+
def new_ostruct_member(name)
|
167
|
+
name = name.to_sym
|
168
|
+
unless respond_to?(name)
|
169
|
+
define_singleton_method(name) { @table[name] }
|
170
|
+
define_singleton_method("#{name}=") { |x| modifiable[name] = x }
|
171
|
+
end
|
172
|
+
name
|
173
|
+
end
|
174
|
+
protected :new_ostruct_member
|
175
|
+
|
176
|
+
def method_missing(mid, *args) # :nodoc:
|
177
|
+
mname = mid.id2name
|
178
|
+
len = args.length
|
179
|
+
if mname.chomp!('=')
|
180
|
+
if len != 1
|
181
|
+
raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
|
182
|
+
end
|
183
|
+
modifiable[new_ostruct_member(mname)] = args[0]
|
184
|
+
elsif len == 0
|
185
|
+
@table[mid]
|
186
|
+
else
|
187
|
+
raise NoMethodError, "undefined method `#{mid}' for #{self}", caller(1)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Returns the value of a member.
|
192
|
+
#
|
193
|
+
# person = OpenStruct.new('name' => 'John Smith', 'age' => 70)
|
194
|
+
# person[:age] # => 70, same as ostruct.age
|
195
|
+
#
|
196
|
+
def [](name)
|
197
|
+
@table[name.to_sym]
|
198
|
+
end
|
199
|
+
|
200
|
+
#
|
201
|
+
# Sets the value of a member.
|
202
|
+
#
|
203
|
+
# person = OpenStruct.new('name' => 'John Smith', 'age' => 70)
|
204
|
+
# person[:age] = 42 # => equivalent to ostruct.age = 42
|
205
|
+
# person.age # => 42
|
206
|
+
#
|
207
|
+
def []=(name, value)
|
208
|
+
modifiable[new_ostruct_member(name)] = value
|
209
|
+
end
|
210
|
+
|
211
|
+
#
|
212
|
+
# Remove the named field from the object. Returns the value that the field
|
213
|
+
# contained if it was defined.
|
214
|
+
#
|
215
|
+
# require 'ostruct'
|
216
|
+
#
|
217
|
+
# person = OpenStruct.new('name' => 'John Smith', 'age' => 70)
|
218
|
+
#
|
219
|
+
# person.delete_field('name') # => 'John Smith'
|
220
|
+
#
|
221
|
+
def delete_field(name)
|
222
|
+
sym = name.to_sym
|
223
|
+
singleton_class.__send__(:remove_method, sym, "#{name}=")
|
224
|
+
@table.delete sym
|
225
|
+
end
|
226
|
+
|
227
|
+
InspectKey = :__inspect_key__ # :nodoc:
|
228
|
+
|
229
|
+
#
|
230
|
+
# Returns a string containing a detailed summary of the keys and values.
|
231
|
+
#
|
232
|
+
def inspect
|
233
|
+
str = "#<#{self.class}"
|
234
|
+
|
235
|
+
ids = (Thread.current[InspectKey] ||= [])
|
236
|
+
if ids.include?(object_id)
|
237
|
+
return str << ' ...>'
|
238
|
+
end
|
239
|
+
|
240
|
+
ids << object_id
|
241
|
+
begin
|
242
|
+
first = true
|
243
|
+
for k,v in @table
|
244
|
+
str << "," unless first
|
245
|
+
first = false
|
246
|
+
str << " #{k}=#{v.inspect}"
|
247
|
+
end
|
248
|
+
return str << '>'
|
249
|
+
ensure
|
250
|
+
ids.pop
|
251
|
+
end
|
252
|
+
end
|
253
|
+
alias :to_s :inspect
|
254
|
+
|
255
|
+
attr_reader :table # :nodoc:
|
256
|
+
protected :table
|
257
|
+
|
258
|
+
#
|
259
|
+
# Compares this object and +other+ for equality. An OpenStruct is equal to
|
260
|
+
# +other+ when +other+ is an OpenStruct and the two objects' Hash tables are
|
261
|
+
# equal.
|
262
|
+
#
|
263
|
+
def ==(other)
|
264
|
+
return false unless other.kind_of?(OpenStruct)
|
265
|
+
@table == other.table
|
266
|
+
end
|
267
|
+
|
268
|
+
#
|
269
|
+
# Compares this object and +other+ for equality. An OpenStruct is eql? to
|
270
|
+
# +other+ when +other+ is an OpenStruct and the two objects' Hash tables are
|
271
|
+
# eql?.
|
272
|
+
#
|
273
|
+
def eql?(other)
|
274
|
+
return false unless other.kind_of?(OpenStruct)
|
275
|
+
@table.eql?(other.table)
|
276
|
+
end
|
277
|
+
|
278
|
+
# Compute a hash-code for this OpenStruct.
|
279
|
+
# Two hashes with the same content will have the same hash code
|
280
|
+
# (and will be eql?).
|
281
|
+
def hash
|
282
|
+
@table.hash
|
283
|
+
end
|
284
|
+
end
|