simplemapper 0.0.6 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ require 'rake/gempackagetask'
6
6
  require 'rake/contrib/rubyforgepublisher'
7
7
 
8
8
  PKG_NAME = 'simplemapper'
9
- PKG_VERSION = "0.0.6"
9
+ PKG_VERSION = "0.1.2"
10
10
 
11
11
  PKG_FILES = FileList[
12
12
  "lib/**/*", "rspec/**/*", "[A-Z]*", "Rakefile", "doc/**/*"
@@ -57,6 +57,8 @@ spec = Gem::Specification.new do |s|
57
57
  s.email = "gems@behindlogic.com"
58
58
  s.homepage = "http://simplemapper.rubyforge.org"
59
59
  s.rubyforge_project = 'simplemapper'
60
+ s.add_dependency 'hash_magic'
61
+ s.add_dependency 'formattedstring'
60
62
  end
61
63
 
62
64
  Rake::GemPackageTask.new(spec) do |pkg|
@@ -35,30 +35,41 @@ module SimpleMapper
35
35
  raise TypeError, "options must be a hash!" unless options.is_a?(Hash)
36
36
  @finder_options = options
37
37
  end
38
+ alias :set_finder_options :finder_options=
39
+ def display_options
40
+ @display_options ||= {}
41
+ end
42
+ def display_options=(options)
43
+ raise TypeError, "options must be a hash!" unless options.is_a?(Hash)
44
+ @display_options = options
45
+ end
46
+ alias :set_display_options :display_options=
38
47
 
39
- def get(find_options={})
40
- options = finder_options.merge(find_options)
41
- query = options.empty? ? '' : ('?' + options.to_query)
48
+ def get(options={})
49
+ raw_get(base_uri.path + query_string_from_options(finder_options.merge(display_options.merge(options))))
50
+ end
51
+
52
+ def raw_get(url)
42
53
  begin
43
- http.request(request('get', base_uri.path + query)).body
54
+ http.request(request('get', url)).body
44
55
  rescue => e
45
56
  raise e if !!raise_http_errors
46
57
  nil
47
58
  end
48
59
  end
49
60
 
50
- def put(identifier,data)
61
+ def put(identifier,data,options={})
51
62
  begin
52
- http.request(request('put', URI.parse(identifier).path, data)).body
63
+ http.request(request('put', URI.parse(identifier).path + query_string_from_options(display_options.merge(options)), data)).body
53
64
  rescue => e
54
65
  raise e if !!raise_http_errors
55
66
  nil
56
67
  end
57
68
  end
58
69
 
59
- def post(data)
70
+ def post(data,options={})
60
71
  begin
61
- http.request(request('post', base_uri.path, data)).body
72
+ http.request(request('post', base_uri.path + query_string_from_options(display_options.merge(options)), data)).body
62
73
  rescue => e
63
74
  raise e if !!raise_http_errors
64
75
  nil
@@ -66,9 +77,9 @@ module SimpleMapper
66
77
  end
67
78
 
68
79
  # In the http adapter, the identifier is a url.
69
- def delete(identifier)
80
+ def delete(identifier,options={})
70
81
  begin
71
- http.request(request('delete', URI.parse(identifier).path)).body
82
+ http.request(request('delete', URI.parse(identifier).path + query_string_from_options(options))).body
72
83
  rescue => e
73
84
  raise e if !!raise_http_errors
74
85
  nil
@@ -76,6 +87,10 @@ module SimpleMapper
76
87
  end
77
88
 
78
89
  private
90
+ def query_string_from_options(options={})
91
+ options.empty? ? '' : ('?' + options.to_query)
92
+ end
93
+
79
94
  def http(refresh=false)
80
95
  @http = Net::HTTP.new(base_uri.host, base_uri.port) if @http.nil? || refresh
81
96
  @http
@@ -8,7 +8,7 @@ module SimpleMapper
8
8
  end
9
9
  def run_callback(name, *args)
10
10
  args = args.first if args.length == 1
11
- callbacks[name].inject(args) {|args,cb| cb.call(*args)}
11
+ callbacks[name].inject(args) {|args,cb| cb.call(*args) || args}
12
12
  end
13
13
  end
14
14
  class HttpAdapter
@@ -1,4 +1,4 @@
1
- gem 'oauth'
1
+ require 'rubygems'
2
2
  require 'oauth'
3
3
  require 'oauth/consumer'
4
4
  require 'oauth/client/net_http'
@@ -106,9 +106,8 @@ class OAuthController
106
106
 
107
107
  def initialize(controller, model, consumer_key, consumer_secret, options={})
108
108
  @controller = controller
109
- @model = model
110
109
  @options = DEFAULT_OPTIONS.merge(options)
111
- @model = @options.delete(:model)
110
+ @model = @options.delete(:model) || model
112
111
  @consumer = OAuth::Consumer.new(consumer_key, consumer_secret, options)
113
112
  end
114
113
 
@@ -130,6 +129,8 @@ class OAuthController
130
129
  elsif request_token
131
130
  return @controller.begin_pathway(@options[:authorization_method].in_context(controller).call(@model)) if @options[:authorization_method].is_a?(Proc)
132
131
  return true if access_token # For scriptables
132
+ else
133
+ raise RuntimeError, "It seems there is a problem between your OAuth client and the OAuth provider you are contacting. Inspect the naming of the token and token secret parameters being sent by the website."
133
134
  end
134
135
  end
135
136
 
@@ -1,3 +1,4 @@
1
+ require 'cgi'
1
2
  # This simply defines some extensions to various objects to enable smartly converting values to a
2
3
  # standard query string understandable by Rails and Merb.
3
4
  # First the structural base:
@@ -19,6 +19,10 @@ module SimpleMapper
19
19
  json
20
20
  end
21
21
 
22
+ def meta
23
+ @meta
24
+ end
25
+
22
26
  module ClassMethods
23
27
  # This assumes a standard json format:
24
28
  # {'person':{'attribute':'','another_att':'value'}}
@@ -27,20 +31,32 @@ module SimpleMapper
27
31
  def from_json(json)
28
32
  doc = Serialize.hash_from_json(json)
29
33
  # doc could include a single 'model' element, or a 'models' wrapper around several.
30
- puts "Top-level JSON key(s): #{doc.keys.inspect}" if @debug
31
34
  if doc.is_a?(Hash)
32
- key = doc.keys.first
33
- if doc[key] && doc[key].keys.uniq == [key.singularize] && doc[key][key.singularize].is_a?(Array)
34
- puts "Several objects returned under key '#{key}'/'#{key.singularize}':" if @debug
35
- doc[key][key.singularize].collect do |e|
36
- puts "Obj: #{e.inspect}" if @debug
37
- Object.module_eval("::#{key.singularize.camelize}", __FILE__, __LINE__).load(e)
35
+ # In contrast to XML, JSON is not restricted to one top-level key. We will assume the objects are in a hash/array
36
+ # referenced by either singular or plural of the klass.
37
+ # puts "Received #{doc.length} bytes of json. Top keys: #{doc.keys.join(', ')}. Looking for '#{self.entity_name.underscore}' or '#{self.entity_name.pluralize.underscore}'"
38
+ meta = doc.dup
39
+ key = if doc[self.entity_name.underscore]
40
+ self.entity_name.underscore
41
+ elsif doc[self.entity_name.pluralize.underscore]
42
+ self.entity_name.pluralize.underscore
43
+ end
44
+ return nil if key.nil?
45
+ # puts "JSON has #{key}"
46
+ meta.delete(key) # removing the data leaves us only the meta information
47
+ meta.freeze
48
+ ret = if doc[key].is_a?(Array)
49
+ doc[key].collect do |e|
50
+ obj = self.load(e)
51
+ obj.instance_variable_set(:@meta, meta)
52
+ obj
38
53
  end
39
- elsif doc[key] # top-level must be single object
40
- Object.module_eval("::#{key.singularize.camelize}", __FILE__, __LINE__).load(doc[self.name.underscore])
41
54
  else
42
- nil
55
+ obj = self.load(doc[key])
56
+ obj.instance_variable_set(:@meta, meta)
57
+ obj
43
58
  end
59
+ # puts "Collected: #{(ret.is_a?(Array) ? ret : [ret]).length} objects"; ret
44
60
  else # doc isn't a hash, probably nil
45
61
  doc
46
62
  end
@@ -38,6 +38,7 @@ module SimpleMapper
38
38
  # doc could include a single 'model' element, or a 'models' wrapper around several.
39
39
  puts "Top-level XML key(s): #{doc.keys.inspect}" if @debug
40
40
  if doc.is_a?(Hash)
41
+ # By specification the doc should have only ONE top-level element (key)
41
42
  key = doc.keys.first
42
43
  if doc[key] && doc[key].keys.uniq == [key.singularize] && doc[key][key.singularize].is_a?(Array)
43
44
  puts "Several objects returned under key '#{key}'/'#{key.singularize}':" if @debug
@@ -46,7 +47,7 @@ module SimpleMapper
46
47
  Object.module_eval("::#{key.singularize.camelize}", __FILE__, __LINE__).load(e)
47
48
  end
48
49
  elsif doc[key] # top-level must be single object
49
- Object.module_eval("::#{key.singularize.camelize}", __FILE__, __LINE__).load(doc[self.name.underscore])
50
+ Object.module_eval("::#{key.singularize.camelize}", __FILE__, __LINE__).load(doc[key])
50
51
  else
51
52
  nil
52
53
  end
@@ -53,6 +53,12 @@ module SimpleMapper
53
53
  alias :set_format :format=
54
54
  attr_reader :format_name
55
55
 
56
+ def entity_name
57
+ @entity_name ||= name
58
+ end
59
+ attr_writer :entity_name
60
+ alias :set_entity_name :entity_name=
61
+
56
62
  def connections
57
63
  @connections ||= {}
58
64
  end
@@ -70,10 +76,42 @@ module SimpleMapper
70
76
  end
71
77
 
72
78
  # get
79
+ # Works with pagination, provided the last object in the returned array responds to .meta and that meta information
80
+ # includes a total and a url for the next page of results.
73
81
  def get(*args)
74
82
  adapter = adapter_from_args(*args)
75
83
  objs = extract_from(connection(adapter || :default).get(*args))
76
- objs.is_a?(Array) ? objs.each {|e| e.instance_variable_set(:@adapter, adapter)} : objs.instance_variable_set(:@adapter, adapter) if adapter
84
+ # puts "#{objs ? objs.length : 0} objects."
85
+ # puts "Like #{objs[0].inspect}" if objs
86
+ if objs.is_a?(Array)
87
+ safe = 1
88
+ while(objs[-1].respond_to?(:meta) && objs[-1].meta['total'] > objs.length && objs[-1].meta['next'])
89
+ safe += 1
90
+ objs.concat(extract_from(connection(adapter || :default).raw_get(objs[-1].meta['next'])))
91
+ break if safe >= 50 # Safeguard: if we do 50 requests we're probably on a runaway. Paginating at 50/page would be 2500 records...
92
+ end
93
+ objs.each {|e| e.instance_variable_set(:@adapter, adapter)} if adapter
94
+ else
95
+ objs.instance_variable_set(:@adapter, adapter) if adapter
96
+ end
97
+ # puts "TOTAL #{objs ? objs.length : 0} objects in all."
98
+ # puts "Like #{objs[-1].inspect}" if objs
99
+ objs
100
+ end
101
+
102
+ def get_from_url(url, adapter=:default)
103
+ objs = extract_from(connection(adapter).raw_get(url))
104
+ if objs.is_a?(Array)
105
+ safe = 1
106
+ while(objs[-1].respond_to?(:meta) && objs[-1].meta['total'] > objs.length && objs[-1].meta['next'])
107
+ safe += 1
108
+ objs.concat(extract_from(connection(adapter || :default).raw_get(objs[-1].meta['next'])))
109
+ break if safe >= 50 # Safeguard: if we do 50 requests we're probably on a runaway. Paginating at 50/page would be 2500 records...
110
+ end
111
+ objs.each {|e| e.instance_variable_set(:@adapter, adapter)} if adapter
112
+ else
113
+ objs.instance_variable_set(:@adapter, adapter) if adapter
114
+ end
77
115
  objs
78
116
  end
79
117
 
@@ -144,13 +182,17 @@ module SimpleMapper
144
182
 
145
183
  # sends a put request with self.data
146
184
  def put(*args)
147
- self.data = self.class.extract_one(self.class.connection(@adapter || :default).put(identifier, formatted_data), identifier).to_hash
185
+ new_rec = self.class.extract_one(self.class.connection(@adapter || :default).put(identifier, formatted_data, *args), identifier)
186
+ raise "Request did not return an object" if new_rec.nil?
187
+ self.data = new_rec.to_hash
148
188
  self
149
189
  end
150
190
 
151
191
  # sends a post request with self.data
152
192
  def post(*args)
153
- self.data = self.class.extract_one(self.class.connection(@adapter || :default).post(formatted_data)).to_hash
193
+ new_rec = self.class.extract_one(self.class.connection(@adapter || :default).post(formatted_data, *args))
194
+ raise "Request did not return an object" if new_rec.nil?
195
+ self.data = new_rec.to_hash
154
196
  @persisted = true
155
197
  self
156
198
  end
@@ -15,9 +15,6 @@ module Serialize
15
15
  :default_root => 'xml',
16
16
  }
17
17
  def self.object_to_xml(obj, options={})
18
- # Automatically set the key_name for DataMapper objects
19
- options.merge!(:key_name => obj.class.table.key.name) if obj.class.respond_to?(:table) && obj.class.respond_to?(:persistent?) && obj.class.persistent?
20
- # Should do the above also for ActiveRecord...
21
18
  options = options.reverse_merge!(obj.class.xml_options) if obj.class.respond_to?(:xml_options)
22
19
  options = options.reverse_merge!(obj.xml_options) if obj.respond_to?(:xml_options)
23
20
  to_xml(obj.to_hash(options), {:root => Inflector.underscore(obj.class.name)}.merge(options))
@@ -25,7 +22,6 @@ module Serialize
25
22
  def self.to_xml(attributes={}, options={})
26
23
  options = XML_OPTIONS.merge(options)
27
24
  root = options[:root]
28
- options[:exclude] = [options[:exclude]].flatten.compact.collect {|e| e.to_s}
29
25
  attributes.reject! {|k,v| options[:exclude].include?(k)}
30
26
 
31
27
  doc = REXML::Document.new
@@ -68,21 +64,16 @@ module Serialize
68
64
  :class_columns => :visible_properties,
69
65
  }
70
66
  def self.object_to_json(obj, options={})
71
- # Automatically set the key_name for DataMapper objects
72
- options.merge!(:key_name => obj.class.table.key.name) if obj.class.respond_to?(:table) && obj.class.respond_to?(:persistent?) && obj.class.persistent?
73
- # Should do the above also for ActiveRecord...
74
67
  options = options.reverse_merge!(obj.class.json_options) if obj.class.respond_to?(:json_options)
75
68
  options = options.reverse_merge!(obj.json_options) if obj.respond_to?(:json_options)
76
- to_json(obj.to_hash(options), {:root => Inflector.underscore(obj.class.name)}.merge(options))
69
+ to_json(obj.to_hash(options), options)
77
70
  end
78
71
  def self.to_json(attributes={}, options={})
79
- # max-results=25 || limit=25
80
- # start-index=26 || offset=26
81
- raise NotImplemented
72
+ attributes = attributes.to_hash if attributes.respond_to?(:to_hash)
82
73
  options = JSON_OPTIONS.merge(options)
83
- root = options[:root]
84
74
  options[:exclude] = [options[:exclude]].flatten.compact.collect {|e| e.to_s}
85
75
  attributes.reject! {|k,v| options[:exclude].include?(k)}
76
+ options[:root] ? JSON.unparse({options[:root] => attributes}) : JSON.unparse(attributes)
86
77
  end
87
78
  def self.hash_from_json(json,options={})
88
79
  json.to_s.formatted(:json).to_hash
@@ -91,9 +82,8 @@ end
91
82
 
92
83
  class Hash
93
84
  def to_xml(options={})
94
- options[:root] = keys[0] if keys.length == 1 && (!options.has_key?(:root) || options[:root].to_s == '')
95
- options[:root] = 'xml' if options[:root].to_s == ''
96
- Serialize.to_xml(self.slashed, options)
85
+ options[:root] = keys.length == 1 ? keys[0] : nil if !options.has_key?(:root)
86
+ Serialize.to_xml(self.slashed, options.merge(:root => (options.has_key?(:root) ? options[:root] : 'xml')))
97
87
  end
98
88
  end
99
89
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simplemapper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Parker
@@ -9,10 +9,29 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-05-09 00:00:00 -04:00
12
+ date: 2009-06-01 00:00:00 -04:00
13
13
  default_executable:
14
- dependencies: []
15
-
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hash_magic
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: formattedstring
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
16
35
  description: The lightest of ORMs excluding magic such as properties, primary keys, associations, validations.
17
36
  email: gems@behindlogic.com
18
37
  executables: []
@@ -70,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
70
89
  requirements: []
71
90
 
72
91
  rubyforge_project: simplemapper
73
- rubygems_version: 1.0.1
92
+ rubygems_version: 1.3.1
74
93
  signing_key:
75
94
  specification_version: 2
76
95
  summary: The lightest of ORMs excluding magic such as properties, primary keys, associations, validations.