rdropbox 1.0.0

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.
@@ -0,0 +1,109 @@
1
+ # Defines the Dropbox::Event class.
2
+
3
+ nil # doc fix
4
+
5
+ module Dropbox
6
+
7
+ # The Dropbox::Event class stores information about which entries were
8
+ # modified during a pingback event. You initialize this class from the JSON
9
+ # string given to you by Dropbox during a pingback:
10
+ #
11
+ # event = Dropbox::Event.new(params[:target_events])
12
+ #
13
+ # Once this is complete, the Dropbox::Event instance contains references for
14
+ # each of the entries, with the basic information included in the pingback:
15
+ #
16
+ # event.user_ids #=> [ 1, 2, 3 ]
17
+ # event.entries(1).first #=> #<Dropbox::Revision 1:10:100>
18
+ #
19
+ # For any of these entries, you can load its content and metadata:
20
+ #
21
+ # event.entries(1).first.load(dropbox_session)
22
+ # event.entries(1).first.content #=> "Content of file..."
23
+ # event.entries(1).first.size #=> 2245
24
+ #
25
+ # You can also load only the metadata for all of a user's entries:
26
+ #
27
+ # event.load_metadata(first_users_dropbox_session)
28
+ # event.entries(1).first.size #=> 154365
29
+
30
+ class Event
31
+ def initialize(json_pingback) # :nodoc:
32
+ @json_pingback = json_pingback
33
+ begin
34
+ @metadata = JSON.parse(json_pingback).stringify_keys_recursively
35
+ rescue JSON::ParserError
36
+ raise Dropbox::ParseError, "Invalid pingback event data"
37
+ end
38
+
39
+ process_pingback
40
+ end
41
+
42
+ # Returns an array of Dropbox user ID's involved in this pingback.
43
+
44
+ def user_ids
45
+ @entries_by_user_id.keys
46
+ end
47
+
48
+ # When given no arguments, returns an array of all entries (as
49
+ # Dropbox::Revision instances). When given a user ID, filters the list
50
+ # to only entries belonging to that Dropbox user.
51
+
52
+ def entries(user_id=nil)
53
+ user_id ? (@entries_by_user_id[user_id.to_i] || []).dup : @entries.dup
54
+ end
55
+
56
+ # Loads the metadata for all entries belonging to a given Dropbox session.
57
+ # Does not load data for files that do not belong to the user owning the
58
+ # given Dropbox session.
59
+ #
60
+ # Future calls to this method will result in additional network requests,
61
+ # though the Dropbox::Revision instances do cache their metadata values.
62
+ #
63
+ # Options:
64
+ #
65
+ # +mode+:: Temporarily changes the API mode. See the Dropbox::API::MODES
66
+ # array.
67
+
68
+ def load_metadata(session, options={})
69
+ process_metadata session.event_metadata(@json_pingback, options).stringify_keys_recursively
70
+ end
71
+
72
+ def inspect # :nodoc:
73
+ "#<#{self.class.to_s} (#{@entries.size} entries)>"
74
+ end
75
+
76
+ private
77
+
78
+ def process_pingback
79
+ @entries = Array.new
80
+ @entries_by_user_id = Hash.new
81
+ @entries_hashed = Hash.new
82
+
83
+ @metadata.each do |user_id, namespaces|
84
+ @entries_hashed[user_id.to_i] = Hash.new
85
+ @entries_by_user_id[user_id.to_i] = Array.new
86
+ namespaces.each do |namespace_id, journals|
87
+ @entries_hashed[user_id.to_i][namespace_id.to_i] = Hash.new
88
+ journals.each do |journal_id|
89
+ entry = Dropbox::Revision.new(user_id.to_i, namespace_id.to_i, journal_id.to_i)
90
+ @entries << entry
91
+ @entries_by_user_id[user_id.to_i] << entry
92
+ @entries_hashed[user_id.to_i][namespace_id.to_i][journal_id.to_i] = entry
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ def process_metadata(metadata)
99
+ p metadata
100
+ metadata.each do |user_id, namespaces|
101
+ namespaces.each do |namespace_id, journals|
102
+ journals.each do |journal_id, attributes|
103
+ @entries_hashed[user_id.to_i][namespace_id.to_i][journal_id.to_i].process_metadata(attributes.symbolize_keys)
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,98 @@
1
+ # Defines the Dropbox::Memoization module.
2
+
3
+ nil # doc fix
4
+
5
+ module Dropbox
6
+
7
+ # Adds methods to the Dropbox::Session class to support the temporary local
8
+ # storage of API results to reduce the number of network calls and simplify
9
+ # code.
10
+ #
11
+ # Memoization is <b>opt-in</b>; you must explicitly indicate that you want
12
+ # this functionality by calling the enable_memoization method on your
13
+ # Scribd::Session instance. Once memoization is enabled, subsequent calls to
14
+ # memoized methods will hit an in-memory cache as opposed to making identical
15
+ # network calls.
16
+ #
17
+ # If you would like to use your own caching strategy (for instance, your own
18
+ # memcache instance), set the +cache_proc+ and +cache_clear_proc+ attributes.
19
+ #
20
+ # Enabling memoization makes removes an instance's thread-safety.
21
+ #
22
+ # Example:
23
+ #
24
+ # session.metadata('file1') # network
25
+ #
26
+ # session.enable_memoization
27
+ # session.metadata('file1') # network
28
+ # session.metadata('file1') # cache
29
+ #
30
+ # session.metadata('file2') # network
31
+ # session.metadata('file2') # cache
32
+ #
33
+ # session.disable_memoization
34
+ # session.metadata('file2') # network
35
+
36
+ module Memoization
37
+ def self.included(base) # :nodoc:
38
+ base.extend ClassMethods
39
+ end
40
+
41
+ # The cache_proc is a proc with two arguments, the cache identifier and the
42
+ # proc to call and store in the event of a cache miss:
43
+ #
44
+ # instance.cache_proc = Proc.new do |identifier, calculate_proc|
45
+ # Rails.cache.fetch(identifier) { calculate_proc.call }
46
+ # end
47
+ #
48
+ # The Cache identifier will always be 64 lowercase hexadecimal characters.
49
+ # The second argument is a curried proc including all arguments to the
50
+ # original method call.
51
+
52
+ def cache_proc=(prc)
53
+ @_memo_cache_proc = prc
54
+ end
55
+
56
+ # The cache_clear_proc takes an identifier and should invalidate it from the
57
+ # cache:
58
+ #
59
+ # instance.cache_clear_proc = Proc.new { |identifier| Rails.cache.delete identifier }
60
+
61
+ def cache_clear_proc=(prc)
62
+ @_memo_cache_clear_proc = prc
63
+ end
64
+
65
+ # Begins memoizing the results of API calls. Memoization is off by default
66
+ # for new instances.
67
+
68
+ def enable_memoization
69
+ @_memoize = true
70
+ @_memo_identifiers ||= Set.new
71
+ end
72
+
73
+ # Halts memoization of API calls and clears the memoization cache.
74
+
75
+ def disable_memoization
76
+ @_memoize = false
77
+ @_memo_identifiers.each { |identifier| (@_memo_cache_clear_proc || Proc.new { |ident| eval "@_memo_#{ident} = nil" }).call(identifier) }
78
+ @_memo_identifiers.clear
79
+ end
80
+
81
+ module ClassMethods # :nodoc:
82
+ def memoize(*method_names) # :nodoc:
83
+ method_names.each do |meth|
84
+ define_method :"#{meth}_with_memo" do |*args|
85
+ if @_memoize then
86
+ identifier = Digest::SHA1.hexdigest(meth.to_s + ":" + args.to_yaml)
87
+ @_memo_identifiers << identifier
88
+ (@_memo_cache_proc || Proc.new { |ident, calculate_proc| eval "@_memo_#{ident} ||= calculate_proc.call" }).call identifier, Proc.new { send :"#{meth}_without_memo", *args }
89
+ else
90
+ send :"#{meth}_without_memo", *args
91
+ end
92
+ end
93
+ alias_method_chain meth, :memo
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,197 @@
1
+ # Defines the Dropbox::Revision class.
2
+
3
+ nil # doc fix
4
+
5
+ module Dropbox
6
+
7
+ # A file or folder at a point in time as referenced by a Dropbox::Event
8
+ # pingback event. Instances start out as "shells" only storing enough
9
+ # information to uniquely identify a file/folder belonging to a user at a
10
+ # certain revision.
11
+ #
12
+ # Instances of this class only appear in a Dropbox::Event object. To load the
13
+ # metadata for a revision, use the Dropbox::Event#load_metadata method.
14
+ # To load the content of the file at this revision, use the load_content
15
+ # method on this class.
16
+ #
17
+ # Once the metadata has been loaded, you can access it directly:
18
+ #
19
+ # revision.size #=> 2962
20
+ #
21
+ # The +mtime+ attribute will be a Time instance. The +mtime+ and +size+
22
+ # attributes will be +nil+ (not -1) if the file was deleted. All other
23
+ # attributes are as defined in
24
+ # http://developers.getdropbox.com/base.html#event-metadata
25
+ #
26
+ # If the metadata could not be read for whatever reason, the HTTP error code
27
+ # will be stored in the +error+ attribute.
28
+
29
+ class Revision
30
+ # The ID of the Dropbox user that owns this file.
31
+ attr_reader :user_id
32
+ # The namespace ID of the file (Dropbox internal).
33
+ attr_reader :namespace_id
34
+ # The journal ID of the file (Dropbox internal).
35
+ attr_reader :journal_id
36
+ # The HTTP error code received when trying to load metadata, or +nil+ if no
37
+ # error has yet been received.
38
+ attr_reader :error
39
+
40
+ def initialize(uid, nid, jid) # :nodoc:
41
+ @user_id = uid
42
+ @namespace_id = nid
43
+ @journal_id = jid
44
+ end
45
+
46
+ # The unique identifier string used by some Dropbox event API methods.
47
+
48
+ def identifier
49
+ "#{user_id}:#{namespace_id}:#{journal_id}"
50
+ end
51
+
52
+ # Uses the given Dropbox::Session to load the content and metadata for a
53
+ # file at a specific revision.
54
+ #
55
+ # Options:
56
+ #
57
+ # +mode+:: Temporarily changes the API mode. See the Dropbox::API::MODES
58
+ # array.
59
+
60
+ def load(session, options={})
61
+ @content, @metadata = session.event_content(identifier, options)
62
+ @metadata.symbolize_keys!
63
+
64
+ postprocess_metadata
65
+ end
66
+
67
+ # Returns true if the content for this revision has been previously loaded
68
+ # and is cached in this object.
69
+
70
+ def content_loaded?
71
+ @content.to_bool
72
+ end
73
+
74
+ # Returns true if the metadata for this revision has been previously loaded
75
+ # and is cached in this object.
76
+
77
+ def metadata_loaded?
78
+ @metadata.to_bool
79
+ end
80
+
81
+ # Sugar for the +latest+ attribute.
82
+
83
+ def latest?
84
+ raise NotLoadedError.new(:metadata) unless metadata_loaded?
85
+ self.latest
86
+ end
87
+
88
+ # Sugar for the +is_dir+ attribute.
89
+
90
+ def directory?
91
+ raise NotLoadedError.new(:metadata) unless metadata_loaded?
92
+ self.is_dir
93
+ end
94
+
95
+ # Synonym for the +mtime+ attribute, for "duck" compatibility with the
96
+ # Dropbox +metadata+ API.
97
+
98
+ def modified
99
+ raise NotLoadedError.new(:metadata) unless metadata_loaded?
100
+ self.mtime
101
+ end
102
+
103
+ # Returns true if an error occurred when trying to load metadata.
104
+
105
+ def error?
106
+ error.to_bool
107
+ end
108
+
109
+ # Returns true if this change represents the file being deleted.
110
+
111
+ def deleted?
112
+ raise NotLoadedError.new(:metadata) unless metadata_loaded?
113
+ self.mtime.nil? and self.size.nil?
114
+ end
115
+
116
+ # Returns the contents of the file as a string. Returns nil for directories.
117
+ # You must call load first to retrieve the content from the network.
118
+
119
+ def content
120
+ raise NotLoadedError.new(:content) unless content_loaded?
121
+ @content
122
+ end
123
+
124
+ def inspect # :nodoc:
125
+ "#<#{self.class.to_s} #{identifier}>"
126
+ end
127
+
128
+ # Allows you to access metadata attributes directly:
129
+ #
130
+ # revision.size #=> 10526
131
+ #
132
+ # A NoMethodError will be raised if the metadata has not yet been loaded for
133
+ # this revision, so be sure to call metadata_loaded? beforehand.
134
+
135
+ def method_missing(meth, *args)
136
+ if args.empty? then
137
+ if @metadata and @metadata.include?(meth) then
138
+ return @metadata[meth]
139
+ else
140
+ super
141
+ end
142
+ else
143
+ super
144
+ end
145
+ end
146
+
147
+ # Loads the metadata for the latest revision of the entry and returns it as
148
+ # as <tt>Struct</tt> object. Uses the given session and calls
149
+ # Dropbox::API.metadata.
150
+ #
151
+ # If the metadata for this object has not yet been loaded, raises an error.
152
+ # Options are passed to Dropbox::API.metadata.
153
+
154
+ def metadata_for_latest_revision(session, options={})
155
+ raise NotLoadedError.new(:metadata) unless metadata_loaded?
156
+ session.metadata self.path, options
157
+ end
158
+
159
+ def process_metadata(metadata) # :nodoc:
160
+ if metadata[:error] then
161
+ @error = metadata[:error] unless @metadata
162
+ return
163
+ end
164
+
165
+ @error = nil
166
+ @metadata = Hash.new
167
+ metadata.each { |key, value| @metadata[key.to_sym] = value }
168
+
169
+ postprocess_metadata
170
+ end
171
+
172
+ private
173
+
174
+ def postprocess_metadata
175
+ @metadata[:size] = nil if @metadata[:size] == -1
176
+ @metadata[:mtime] = (@metadata[:mtime] == -1 ? nil : Time.at(@metadata[:mtime])) if @metadata[:mtime]
177
+ @metadata[:ts] = Time.parse(@metadata[:ts]) if @metadata[:ts]
178
+ end
179
+ end
180
+
181
+ # Raised when trying to access content metadata before it has been loaded.
182
+
183
+ class NotLoadedError < StandardError
184
+
185
+ # What data did you attempt to access before it was loaded? Either
186
+ # <tt>:content</tt> or <tt>:metadata</tt>.
187
+ attr_reader :data
188
+
189
+ def initialize(data) # :nodoc:
190
+ @data = data
191
+ end
192
+
193
+ def to_s # :nodoc:
194
+ "#{data.capitalize} not yet loaded -- call #load on the Dropbox::Revision instance beforehand"
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,160 @@
1
+ # Defines the Dropbox::Session class.
2
+
3
+ require 'oauth'
4
+
5
+ module Dropbox
6
+
7
+ # This class is a portal to the Dropbox API and a façade over the Ruby OAuth
8
+ # gem allowing developers to authenticate their user's Dropbox accounts.
9
+ #
10
+ # == Authenticating a user
11
+ #
12
+ # You start by creating a new instance and providing your OAuth consumer key
13
+ # and secret. You then call the authorize_url method on your new instance to
14
+ # receive the authorization URL.
15
+ #
16
+ # Once your user visits the URL, it will complete the authorization process on
17
+ # the server side. You should call the authorize method:
18
+ #
19
+ # session = Dropbox::Session.new(my_key, my_secret)
20
+ # puts "Now visit #{session.authorize_url}. Hit enter when you have completed authorization."
21
+ # gets
22
+ # session.authorize
23
+ #
24
+ # The authorize method must be called on the same instance of Dropbox::Session
25
+ # that gave you the URL. If this is unfeasible (for instance, you are doing
26
+ # this in a stateless Rails application), you can serialize the Session for
27
+ # storage (e.g., in your Rails session):
28
+ #
29
+ # def authorize
30
+ # if params[:oauth_token] then
31
+ # dropbox_session = Dropbox::Session.deserialize(session[:dropbox_session])
32
+ # dropbox_session.authorize(params)
33
+ # session[:dropbox_session] = dropbox_session.serialize # re-serialize the authenticated session
34
+ #
35
+ # redirect_to :action => 'upload'
36
+ # else
37
+ # dropbox_session = Dropbox::Session.new('your_consumer_key', 'your_consumer_secret')
38
+ # session[:dropbox_session] = dropbox_session.serialize
39
+ # redirect_to dropbox_session.authorize_url(:oauth_callback => root_url)
40
+ # end
41
+ # end
42
+ #
43
+ # == Working with the API
44
+ #
45
+ # This class includes the methods of the Dropbox::API module. See that module
46
+ # to learn how to continue using the API.
47
+
48
+ class Session
49
+ include API
50
+
51
+ # Begins the authorization process. Provide the OAuth key and secret of your
52
+ # API account, assigned by Dropbox. This is the first step in the
53
+ # authorization process.
54
+ #
55
+ # Options:
56
+ #
57
+ # +ssl+:: If true, uses SSL to connect to the Dropbox API server.
58
+
59
+ def initialize(oauth_key, oauth_secret, options={})
60
+ @ssl = options[:ssl].to_bool
61
+ @consumer = OAuth::Consumer.new(oauth_key, oauth_secret,
62
+ :site => (@ssl ? Dropbox::SSL_HOST : Dropbox::HOST),
63
+ :request_token_path => "/#{Dropbox::VERSION}/oauth/request_token",
64
+ :authorize_path => "/#{Dropbox::VERSION}/oauth/authorize",
65
+ :access_token_path => "/#{Dropbox::VERSION}/oauth/access_token")
66
+ @request_token = @consumer.get_request_token
67
+ end
68
+
69
+ # Returns a URL that is used to complete the authorization process. Visiting
70
+ # this URL is the second step in the authorization process, after creating
71
+ # the Session instance.
72
+
73
+ def authorize_url(*args)
74
+ if authorized? then
75
+ raise AlreadyAuthorizedError, "You have already been authorized; no need to get an authorization URL."
76
+ else
77
+ return @request_token.authorize_url(*args)
78
+ end
79
+ end
80
+
81
+ # Authorizes a user from the information returned by Dropbox. This is the
82
+ # third step in the authorization process, after sending the user to the
83
+ # authorize_url.
84
+ #
85
+ # You can pass to this method a hash containing the keys and values of the
86
+ # OAuth parameters returned by Dropbox. An example in Rails:
87
+ #
88
+ # session.authorize :oauth_verifier => params[:oauth_verifier]
89
+ #
90
+ # Returns a boolean indicating if authentication was successful.
91
+
92
+ def authorize(options={})
93
+ @access_token = @request_token.get_access_token(options)
94
+ @request_token = nil if @access_token
95
+ return @access_token.to_bool
96
+ end
97
+
98
+ # Returns true if this session has been authorized.
99
+
100
+ def authorized?
101
+ @access_token.to_bool
102
+ end
103
+
104
+ # Serializes this object into a string that can then be recreated with the
105
+ # Dropbox::Session.deserialize method.
106
+
107
+ def serialize
108
+ if authorized? then
109
+ [ @consumer.key, @consumer.secret, authorized?, @access_token.token, @access_token.secret ].to_yaml
110
+ else
111
+ [ @consumer.key, @consumer.secret, authorized?, @request_token.token, @request_token.secret ].to_yaml
112
+ end
113
+ end
114
+
115
+ # Deserializes an instance from a string created from the serialize method.
116
+ # Returns the recreated instance.
117
+
118
+ def self.deserialize(data)
119
+ consumer_key, consumer_secret, authorized, token, token_secret = YAML.load(StringIO.new(data))
120
+ raise ArgumentError, "Must provide a properly serialized #{self.to_s} instance" unless [ consumer_key, consumer_secret, token, token_secret ].all? and authorized == true or authorized == false
121
+
122
+ session = self.new(consumer_key, consumer_secret)
123
+ if authorized then
124
+ session.instance_variable_set :@access_token, OAuth::AccessToken.new(session.instance_variable_get(:@consumer), token, token_secret)
125
+ else
126
+ session.instance_variable_set :@request_token, OAuth::RequestToken.new(session.instance_variable_get(:@consumer), token, token_secret)
127
+ end
128
+
129
+ return session
130
+ end
131
+
132
+ def inspect # :nodoc:
133
+ "#<#{self.class.to_s} #{@consumer.key} (#{'un' unless authorized?}authorized)>"
134
+ end
135
+
136
+ private
137
+
138
+ def access_token
139
+ @access_token || raise(UnauthorizedError, "You need to authorize the Dropbox user before you can call API methods")
140
+ end
141
+
142
+ def clone_with_host(host)
143
+ session = dup
144
+ consumer = OAuth::Consumer.new(@consumer.key, @consumer.secret, :site => host)
145
+ session.instance_variable_set :@consumer, consumer
146
+ session.instance_variable_set :@access_token, OAuth::AccessToken.new(consumer, @access_token.token, @access_token.secret)
147
+ return session
148
+ end
149
+ end
150
+
151
+ # Raised when trying to call Dropbox API methods without yet having completed
152
+ # the OAuth process.
153
+
154
+ class UnauthorizedError < StandardError; end
155
+
156
+ # Raised when trying to call Dropbox::Session#authorize_url on an already
157
+ # authorized session.
158
+
159
+ class AlreadyAuthorizedError < StandardError; end
160
+ end
data/lib/dropbox.rb ADDED
@@ -0,0 +1,43 @@
1
+ # Defines the Dropbox module.
2
+
3
+ require 'cgi'
4
+ require 'yaml'
5
+ require 'digest/sha1'
6
+ require 'thread'
7
+ require 'set'
8
+ require 'time'
9
+ require 'tempfile'
10
+
11
+ Dir.glob("#{File.expand_path File.dirname(__FILE__)}/extensions/*.rb") { |file| require file }
12
+ Dir.glob("#{File.expand_path File.dirname(__FILE__)}/dropbox/*.rb") { |file| require file }
13
+
14
+ # Container module for the all Dropbox API classes.
15
+
16
+ module Dropbox
17
+ # The API version this client works with.
18
+ VERSION = "0"
19
+ # The host serving API requests.
20
+ HOST = "http://api.dropbox.com"
21
+ # The SSL host serving API requests.
22
+ SSL_HOST = "https://api.dropbox.com"
23
+ # Alternate hosts for other API requests.
24
+ ALTERNATE_HOSTS = { 'event_content' => 'http://api-content.dropbox.com', 'files' => "http://api-content.dropbox.com" }
25
+ # Alternate SSL hosts for other API requests.
26
+ ALTERNATE_SSL_HOSTS = { 'event_content' => 'https://api-content.dropbox.com', 'files' => "https://api-content.dropbox.com" }
27
+
28
+ def self.api_url(*paths_and_options) # :nodoc:
29
+ params = paths_and_options.extract_options!
30
+ ssl = params.delete(:ssl)
31
+ host = (ssl ? ALTERNATE_SSL_HOSTS[paths_and_options.first] : ALTERNATE_HOSTS[paths_and_options.first]) || (ssl ? SSL_HOST : HOST)
32
+ url = "#{host}/#{VERSION}/#{paths_and_options.map { |path_elem| CGI.escape path_elem.to_s }.join('/')}"
33
+ url.gsub! '+', '%20' # dropbox doesn't really like plusses
34
+ url << "?#{params.map { |k,v| CGI.escape(k.to_s) + "=" + CGI.escape(v.to_s) }.join('&')}" unless params.empty?
35
+ return url
36
+ end
37
+
38
+ def self.check_path(path) # :nodoc:
39
+ raise ArgumentError, "Backslashes are not allowed in Dropbox paths" if path.include?('\\')
40
+ raise ArgumentError, "Dropbox paths are limited to 256 characters in length" if path.size > 256
41
+ return path
42
+ end
43
+ end
@@ -0,0 +1,9 @@
1
+ class Array # :nodoc:
2
+ def extract_options! # :nodoc:
3
+ last.is_a?(::Hash) ? pop : {}
4
+ end unless method_defined?(:extract_options!)
5
+
6
+ def to_hash # :nodoc:
7
+ inject({}) { |hsh, (k,v)| hsh[k] = v ; hsh }
8
+ end unless method_defined?(:to_hash)
9
+ end
@@ -0,0 +1,61 @@
1
+ class Hash # :nodoc:
2
+ def slice(*keys) #:nodoc:
3
+ keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key)
4
+ hash = self.class.new
5
+ keys.each { |k| hash[k] = self[k] if has_key?(k) }
6
+ hash
7
+ end unless method_defined?(:slice)
8
+
9
+ def symbolize_keys # :nodoc:
10
+ inject({}) do |options, (key, value)|
11
+ options[(key.to_sym rescue key) || key] = value
12
+ options
13
+ end
14
+ end unless method_defined?(:symbolize_keys)
15
+
16
+ def symbolize_keys! # :nodoc:
17
+ self.replace(self.symbolize_keys)
18
+ end unless method_defined?(:symbolize_keys!)
19
+
20
+ def symbolize_keys_recursively # :nodoc:
21
+ hsh = symbolize_keys
22
+ hsh.each { |k, v| hsh[k] = v.symbolize_keys_recursively if v.kind_of?(Hash) }
23
+ hsh.each { |k, v| hsh[k] = v.map { |i| i.kind_of?(Hash) ? i.symbolize_keys_recursively : i } if v.kind_of?(Array) }
24
+ return hsh
25
+ end
26
+
27
+ def stringify_keys # :nodoc:
28
+ inject({}) do |options, (key, value)|
29
+ options[(key.to_s rescue key) || key] = value
30
+ options
31
+ end
32
+ end unless method_defined?(:stringify_keys)
33
+
34
+ def stringify_keys! # :nodoc:
35
+ self.replace(self.stringify_keys)
36
+ end unless method_defined?(:stringify_keys!)
37
+
38
+ def stringify_keys_recursively # :nodoc:
39
+ hsh = stringify_keys
40
+ hsh.each { |k, v| hsh[k] = v.stringify_keys_recursively if v.kind_of?(Hash) }
41
+ hsh.each { |k, v| hsh[k] = v.map { |i| i.kind_of?(Hash) ? i.stringify_keys_recursively : i } if v.kind_of?(Array) }
42
+ return hsh
43
+ end
44
+
45
+ def to_struct # :nodoc:
46
+ struct = Struct.new(*keys).new(*values)
47
+ # attach methods for any predicate keys, since Struct.new doesn't seem to do that
48
+ pred_keys = slice(*(keys.select { |key| key.to_s.ends_with?('?') }))
49
+ pred_keys.each do |key, val|
50
+ struct.eigenclass.send(:define_method, key.to_sym) { return val }
51
+ end
52
+ return struct
53
+ end
54
+
55
+ def to_struct_recursively # :nodoc:
56
+ hsh = dup
57
+ hsh.each { |k, v| hsh[k] = v.to_struct_recursively if v.kind_of?(Hash) }
58
+ hsh.each { |k, v| hsh[k] = v.map { |i| i.kind_of?(Hash) ? i.to_struct_recursively : i } if v.kind_of?(Array) }
59
+ return hsh.to_struct
60
+ end
61
+ end