parse_resource 1.7.3 → 1.8.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.
Files changed (65) hide show
  1. data/.DS_Store +0 -0
  2. data/.travis.yml +1 -1
  3. data/Gemfile +4 -8
  4. data/Gemfile.lock +27 -23
  5. data/README.md +81 -2
  6. data/Rakefile +9 -8
  7. data/VERSION +1 -1
  8. data/fixtures/.DS_Store +0 -0
  9. data/fixtures/vcr_cassettes/.DS_Store +0 -0
  10. data/fixtures/vcr_cassettes/test_all.yml +319 -34
  11. data/fixtures/vcr_cassettes/test_attribute_getters.yml +256 -12
  12. data/fixtures/vcr_cassettes/test_attribute_setters.yml +256 -12
  13. data/fixtures/vcr_cassettes/test_authenticate.yml +260 -32
  14. data/fixtures/vcr_cassettes/test_chained_wheres.yml +320 -35
  15. data/fixtures/vcr_cassettes/test_chunk.yml +1359 -0
  16. data/fixtures/vcr_cassettes/test_count.yml +495 -84
  17. data/fixtures/vcr_cassettes/test_create.yml +154 -12
  18. data/fixtures/vcr_cassettes/test_created_at.yml +256 -12
  19. data/fixtures/vcr_cassettes/test_destroy.yml +364 -32
  20. data/fixtures/vcr_cassettes/test_destroy_all.yml +236 -48
  21. data/fixtures/vcr_cassettes/test_each.yml +488 -56
  22. data/fixtures/vcr_cassettes/test_fetching_closest_10.yml +1509 -0
  23. data/fixtures/vcr_cassettes/test_fetching_closest_by_kilometers.yml +1509 -0
  24. data/fixtures/vcr_cassettes/test_fetching_closest_by_miles.yml +1509 -0
  25. data/fixtures/vcr_cassettes/test_fetching_closest_by_radians.yml +1509 -0
  26. data/fixtures/vcr_cassettes/test_fetching_closest_within_box.yml +489 -0
  27. data/fixtures/vcr_cassettes/test_fetching_geopoint_field.yml +489 -0
  28. data/fixtures/vcr_cassettes/test_find.yml +312 -24
  29. data/fixtures/vcr_cassettes/test_find_all_by.yml +170 -34
  30. data/fixtures/vcr_cassettes/test_find_by.yml +174 -38
  31. data/fixtures/vcr_cassettes/test_first.yml +260 -23
  32. data/fixtures/vcr_cassettes/test_id.yml +256 -12
  33. data/fixtures/vcr_cassettes/test_installation_creation.yml +199 -0
  34. data/fixtures/vcr_cassettes/test_installation_creation_validation_check.yml +297 -0
  35. data/fixtures/vcr_cassettes/test_limit.yml +1138 -179
  36. data/fixtures/vcr_cassettes/test_map.yml +488 -56
  37. data/fixtures/vcr_cassettes/test_order_ascending.yml +395 -0
  38. data/fixtures/vcr_cassettes/test_order_descending.yml +446 -0
  39. data/fixtures/vcr_cassettes/test_save.yml +316 -24
  40. data/fixtures/vcr_cassettes/test_save_all_and_destroy_all.yml +869 -0
  41. data/fixtures/vcr_cassettes/test_saving_geo_point_with_quick_init.yml +395 -0
  42. data/fixtures/vcr_cassettes/test_saving_geopoint_with_coords.yml +395 -0
  43. data/fixtures/vcr_cassettes/test_skip.yml +120 -525
  44. data/fixtures/vcr_cassettes/test_update.yml +316 -24
  45. data/fixtures/vcr_cassettes/test_updated_at.yml +316 -24
  46. data/fixtures/vcr_cassettes/test_username_should_be_unique.yml +311 -21
  47. data/fixtures/vcr_cassettes/test_where.yml +117 -25
  48. data/lib/kaminari_extension.rb +60 -0
  49. data/lib/parse_resource.rb +4 -2
  50. data/lib/parse_resource/base.rb +262 -163
  51. data/lib/parse_resource/client.rb +8 -0
  52. data/lib/parse_resource/parse_error.rb +36 -22
  53. data/lib/parse_resource/query.rb +99 -7
  54. data/lib/parse_resource/query_methods.rb +64 -0
  55. data/lib/parse_resource/types/parse_geopoint.rb +19 -0
  56. data/parse_resource.gemspec +29 -9
  57. data/parse_resource.yml +2 -2
  58. data/test/active_model_lint_test.rb +0 -2
  59. data/test/helper.rb +13 -3
  60. data/test/test_parse_installation.rb +41 -0
  61. data/test/test_parse_resource.rb +108 -20
  62. data/test/test_parse_user.rb +4 -7
  63. data/test/test_query_options.rb +0 -38
  64. data/test/test_types.rb +186 -0
  65. metadata +38 -31
@@ -0,0 +1,8 @@
1
+ require "rest-client"
2
+ require "json"
3
+
4
+ module ParseResource
5
+ class Client
6
+
7
+ end
8
+ end
@@ -1,34 +1,48 @@
1
1
  class ParseError
2
+ # ParseError actually represents both HTTP & parse.com error codes. If the
3
+ # HTTP response is 400, one can inspect the first element of the error
4
+ # converted to_array for the HTTP error code and the 2nd element for the
5
+ # parse error response.
6
+ attr_accessor :msg, :code, :error
2
7
 
8
+ # @param [String] an error code, e.g. "400"
9
+ # @param [Object] an optional error mesg/object.
3
10
  def initialize(code, msg="")
4
11
  @msg = msg
5
- case code
6
- when 202
7
- @error = [:username, "must be unique"]
8
- when 111
9
- @error = [:base, "field set to incorrect type. Error code #{code}. #{msg}"]
10
- when 125
11
- @error = [:email, "must be valid"]
12
- when 122
13
- @error = [:file_name, "contains only a-zA-Z0-9_. characters and is between 1 and 36 characters."]
14
- when 204
15
- @error = [:email, "must not be missing"]
16
- when 203
17
- @error = [:email, "has already been taken"]
18
- when 200
19
- @error = [:username, "is missing or empty"]
20
- when 201
21
- @error = [:password, "is missing or empty"]
22
- when 205
23
- @error = [:user, "with specified email not found"]
12
+ @code = code
13
+ case code.to_s
14
+ when "111"
15
+ @error = "Invalid type."
16
+ when "135"
17
+ @error = "Unknown device type."
18
+ when "202"
19
+ @error = "Username already taken."
20
+ when "400"
21
+ @error = "Bad Request: The request cannot be fulfilled due to bad syntax."
22
+ when "401"
23
+ @error = "Unauthorized: Check your App ID & Master Key."
24
+ when "403"
25
+ @error = "Forbidden: You do not have permission to access or modify this."
26
+ when "408"
27
+ @error = "Request Timeout: The request was not completed within the time the server was prepared to wait."
28
+ when "415"
29
+ @error = "Unsupported Media Type"
30
+ when "500"
31
+ @error = "Internal Server Error"
32
+ when "502"
33
+ @error = "Bad Gateway"
34
+ when "503"
35
+ @error = "Service Unavailable"
36
+ when "508"
37
+ @error = "Loop Detected"
24
38
  else
25
- raise "Parse error #{code}: #{@error}"
39
+ @error = "Unknown Error"
40
+ raise "Parse error #{code}: #{@error} #{@msg}"
26
41
  end
27
42
  end
28
43
 
29
44
  def to_array
30
- @error[1] = @error[1] + " " + @msg
31
- @error
45
+ [ @code, @msg ]
32
46
  end
33
47
 
34
48
  end
@@ -14,6 +14,9 @@ class Query
14
14
  end
15
15
 
16
16
  def limit(limit)
17
+ # If > 1000, set chunking, because large queries over 1000 need it with Parse
18
+ chunk(1000) if limit > 1000
19
+
17
20
  criteria[:limit] = limit
18
21
  self
19
22
  end
@@ -23,12 +26,15 @@ class Query
23
26
  self
24
27
  end
25
28
 
26
- # deprecating until it works
27
- #def order(attribute)
28
- # attribute = attribute.to_sym if attribute.is_a?(String)
29
- # criteria[:order] = attribute
30
- # self
31
- #end
29
+ def order(attr)
30
+ orders = attr.split(" ")
31
+ if orders.count > 1
32
+ criteria[:order] = orders.last.downcase == "desc" ? "-#{orders.first}" : "#{orders.first}"
33
+ else
34
+ criteria[:order] = orders.first
35
+ end
36
+ self
37
+ end
32
38
 
33
39
  def skip(skip)
34
40
  criteria[:skip] = skip
@@ -37,10 +43,47 @@ class Query
37
43
 
38
44
  def count(count=1)
39
45
  criteria[:count] = count
40
- #self
41
46
  all
42
47
  end
43
48
 
49
+ # Divides the query into multiple chunks if you're running into RestClient::BadRequest errors.
50
+ def chunk(count=100)
51
+ criteria[:chunk] = count
52
+ self
53
+ end
54
+
55
+ def near(klass, geo_point, options)
56
+ if geo_point.is_a? Array
57
+ geo_point = ParseGeoPoint.new :latitude => geo_point[0], :longitude => geo_point[1]
58
+ end
59
+
60
+ query = { "$nearSphere" => geo_point.to_pointer }
61
+ if options[:maxDistanceInMiles]
62
+ query["$maxDistanceInMiles"] = options[:maxDistanceInMiles]
63
+ elsif options[:maxDistanceInRadians]
64
+ query["$maxDistanceInRadians"] = options[:maxDistanceInRadians]
65
+ elsif options[:maxDistanceInKilometers]
66
+ query["$maxDistanceInKilometers"] = options[:maxDistanceInKilometers]
67
+ end
68
+
69
+ criteria[:conditions].merge!({ klass => query })
70
+ self
71
+ end
72
+
73
+ def within_box(klass, geo_point_south, geo_point_north)
74
+ if geo_point_south.is_a? Array
75
+ geo_point_south = ParseGeoPoint.new :latitude => geo_point_south[0], :longitude => geo_point_south[1]
76
+ end
77
+
78
+ if geo_point_north.is_a? Array
79
+ geo_point_north = ParseGeoPoint.new :latitude => geo_point_north[0], :longitude => geo_point_north[1]
80
+ end
81
+
82
+ query = { "$within" => { "$box" => [geo_point_south.to_pointer, geo_point_north.to_pointer]}}
83
+ criteria[:conditions].merge!({ klass => query })
84
+ self
85
+ end
86
+
44
87
  def execute
45
88
  params = {}
46
89
  params.merge!({:where => criteria[:conditions].to_json}) if criteria[:conditions]
@@ -50,6 +93,8 @@ class Query
50
93
  params.merge!({:include => criteria[:include]}) if criteria[:include]
51
94
  params.merge!({:order => criteria[:order]}) if criteria[:order]
52
95
 
96
+ return chunk_results(params) if criteria[:chunk]
97
+
53
98
  resp = @klass.resource.get(:params => params)
54
99
 
55
100
  if criteria[:count] == 1
@@ -61,11 +106,58 @@ class Query
61
106
  end
62
107
  end
63
108
 
109
+ def chunk_results(params={})
110
+ criteria[:limit] ||= 100
111
+
112
+ start_row = criteria[:skip].to_i
113
+ end_row = [criteria[:limit].to_i - start_row - 1, 1].max
114
+ result = []
115
+
116
+ # Start at start_row, go to end_row, get results in chunks
117
+ (start_row..end_row).each_slice(criteria[:chunk].to_i) do |slice|
118
+ params[:skip] = slice.first
119
+ params[:limit] = slice.length # Either the chunk size or the end of the limited results
120
+
121
+ resp = @klass.resource.get(:params => params)
122
+ results = JSON.parse(resp)['results']
123
+ result = result + results.map {|r| @klass.model_name.constantize.new(r, false)}
124
+ break if results.length < params[:limit] # Got back fewer than we asked for, so exit.
125
+ end
126
+ result
127
+ end
128
+
129
+ def first
130
+ limit(1)
131
+ execute.first
132
+ end
133
+
64
134
  def all
65
135
  execute
66
136
  end
67
137
 
68
138
  def method_missing(meth, *args, &block)
139
+ method_name = method_name.to_s
140
+ if method_name.start_with?("find_by_")
141
+ attrib = method_name.gsub(/^find_by_/,"")
142
+ finder_name = "find_all_by_#{attrib}"
143
+
144
+ define_singleton_method(finder_name) do |target_value|
145
+ where({attrib.to_sym => target_value}).first
146
+ end
147
+
148
+ send(finder_name, args[0])
149
+
150
+ elsif method_name.start_with?("find_all_by_")
151
+ attrib = method_name.gsub(/^find_all_by_/,"")
152
+ finder_name = "find_all_by_#{attrib}"
153
+
154
+ define_singleton_method(finder_name) do |target_value|
155
+ where({attrib.to_sym => target_value}).all
156
+ end
157
+
158
+ send(finder_name, args[0])
159
+ end
160
+
69
161
  if Array.method_defined?(meth)
70
162
  all.send(meth, *args, &block)
71
163
  else
@@ -0,0 +1,64 @@
1
+ #require 'parse_resource'
2
+ require 'parse_resource/query'
3
+
4
+ module ParseResource
5
+
6
+ module QueryMethods
7
+
8
+ module ClassMethods
9
+ # Include the attributes of a parent ojbect in the results
10
+ # Similar to ActiveRecord eager loading
11
+ #
12
+ def include_object(parent)
13
+ Query.new(self).include_object(parent)
14
+ end
15
+
16
+ # Add this at the end of a method chain to get the count of objects, instead of an Array of objects
17
+ def count
18
+ #https://www.parse.com/docs/rest#queries-counting
19
+ Query.new(self).count(1)
20
+ end
21
+
22
+ # Find all ParseResource::Base objects for that model.
23
+ #
24
+ # @return [Array] an `Array` of objects that subclass `ParseResource`.
25
+ def all
26
+ Query.new(self).all
27
+ end
28
+
29
+ # Find the first object. Fairly random, not based on any specific condition.
30
+ #
31
+ def first
32
+ Query.new(self).limit(1).first
33
+ end
34
+
35
+ # Limits the number of objects returned
36
+ #
37
+ def limit(n)
38
+ Query.new(self).limit(n)
39
+ end
40
+
41
+ # Skip the number of objects
42
+ #
43
+ def skip(n)
44
+ Query.new(self).skip(n)
45
+ end
46
+
47
+ def order(attr)
48
+ Query.new(self).order(attr)
49
+ end
50
+
51
+ def near(near, geo_point, options={})
52
+ Query.new(self).near(near, geo_point, options)
53
+ end
54
+
55
+ def within_box(near, geo_point_south, geo_point_north)
56
+ Query.new(self).within_box(near, geo_point_south, geo_point_north)
57
+ end
58
+ end
59
+
60
+ def self.included(base)
61
+ base.extend(ClassMethods)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,19 @@
1
+ class ParseGeoPoint
2
+ attr_accessor :latitude, :longitude
3
+
4
+ def initialize(hash=nil)
5
+ if hash.nil?
6
+ self.latitude=0.0
7
+ self.longitude=0.0
8
+ else
9
+ self.latitude = hash["latitude"] || hash[:latitude]
10
+ self.longitude = hash["longitude"] || hash[:longitude]
11
+ end
12
+
13
+ end
14
+
15
+ def to_pointer
16
+ {"__type"=>"GeoPoint", :latitude=> self.latitude, :longitude => self.longitude}
17
+ end
18
+
19
+ end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "parse_resource"
8
- s.version = "1.7.3"
8
+ s.version = "1.8.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Alan deLevie"]
12
- s.date = "2012-09-20"
12
+ s.date = "2013-04-17"
13
13
  s.description = ""
14
14
  s.email = "adelevie@gmail.com"
15
15
  s.extra_rdoc_files = [
@@ -17,6 +17,7 @@ Gem::Specification.new do |s|
17
17
  "README.md"
18
18
  ]
19
19
  s.files = [
20
+ ".DS_Store",
20
21
  ".document",
21
22
  ".travis.yml",
22
23
  "Gemfile",
@@ -25,38 +26,58 @@ Gem::Specification.new do |s|
25
26
  "README.md",
26
27
  "Rakefile",
27
28
  "VERSION",
29
+ "fixtures/.DS_Store",
30
+ "fixtures/vcr_cassettes/.DS_Store",
28
31
  "fixtures/vcr_cassettes/test_all.yml",
29
32
  "fixtures/vcr_cassettes/test_attribute_getters.yml",
30
33
  "fixtures/vcr_cassettes/test_attribute_setters.yml",
31
34
  "fixtures/vcr_cassettes/test_authenticate.yml",
32
35
  "fixtures/vcr_cassettes/test_chained_wheres.yml",
36
+ "fixtures/vcr_cassettes/test_chunk.yml",
33
37
  "fixtures/vcr_cassettes/test_count.yml",
34
38
  "fixtures/vcr_cassettes/test_create.yml",
35
39
  "fixtures/vcr_cassettes/test_created_at.yml",
36
40
  "fixtures/vcr_cassettes/test_destroy.yml",
37
41
  "fixtures/vcr_cassettes/test_destroy_all.yml",
38
42
  "fixtures/vcr_cassettes/test_each.yml",
43
+ "fixtures/vcr_cassettes/test_fetching_closest_10.yml",
44
+ "fixtures/vcr_cassettes/test_fetching_closest_by_kilometers.yml",
45
+ "fixtures/vcr_cassettes/test_fetching_closest_by_miles.yml",
46
+ "fixtures/vcr_cassettes/test_fetching_closest_by_radians.yml",
47
+ "fixtures/vcr_cassettes/test_fetching_closest_within_box.yml",
48
+ "fixtures/vcr_cassettes/test_fetching_geopoint_field.yml",
39
49
  "fixtures/vcr_cassettes/test_find.yml",
40
50
  "fixtures/vcr_cassettes/test_find_all_by.yml",
41
51
  "fixtures/vcr_cassettes/test_find_by.yml",
42
52
  "fixtures/vcr_cassettes/test_first.yml",
43
53
  "fixtures/vcr_cassettes/test_id.yml",
54
+ "fixtures/vcr_cassettes/test_installation_creation.yml",
55
+ "fixtures/vcr_cassettes/test_installation_creation_validation_check.yml",
44
56
  "fixtures/vcr_cassettes/test_limit.yml",
45
57
  "fixtures/vcr_cassettes/test_map.yml",
58
+ "fixtures/vcr_cassettes/test_order_ascending.yml",
59
+ "fixtures/vcr_cassettes/test_order_descending.yml",
46
60
  "fixtures/vcr_cassettes/test_save.yml",
61
+ "fixtures/vcr_cassettes/test_save_all_and_destroy_all.yml",
62
+ "fixtures/vcr_cassettes/test_saving_geo_point_with_quick_init.yml",
63
+ "fixtures/vcr_cassettes/test_saving_geopoint_with_coords.yml",
47
64
  "fixtures/vcr_cassettes/test_skip.yml",
48
65
  "fixtures/vcr_cassettes/test_update.yml",
49
66
  "fixtures/vcr_cassettes/test_updated_at.yml",
50
67
  "fixtures/vcr_cassettes/test_username_should_be_unique.yml",
51
68
  "fixtures/vcr_cassettes/test_where.yml",
52
69
  "lib/.DS_Store",
70
+ "lib/kaminari_extension.rb",
53
71
  "lib/parse_resource.rb",
54
72
  "lib/parse_resource/base.rb",
73
+ "lib/parse_resource/client.rb",
55
74
  "lib/parse_resource/parse_error.rb",
56
75
  "lib/parse_resource/parse_exceptions.rb",
57
76
  "lib/parse_resource/parse_user.rb",
58
77
  "lib/parse_resource/parse_user_validator.rb",
59
78
  "lib/parse_resource/query.rb",
79
+ "lib/parse_resource/query_methods.rb",
80
+ "lib/parse_resource/types/parse_geopoint.rb",
60
81
  "parse_resource.gemspec",
61
82
  "parse_resource.yml",
62
83
  "rdoc/ParseResource.html",
@@ -66,10 +87,12 @@ Gem::Specification.new do |s|
66
87
  "rdoc/rdoc.css",
67
88
  "test/active_model_lint_test.rb",
68
89
  "test/helper.rb",
90
+ "test/test_parse_installation.rb",
69
91
  "test/test_parse_resource.rb",
70
92
  "test/test_parse_user.rb",
71
93
  "test/test_query.rb",
72
- "test/test_query_options.rb"
94
+ "test/test_query_options.rb",
95
+ "test/test_types.rb"
73
96
  ]
74
97
  s.homepage = "http://github.com/adelevie/parse_resource"
75
98
  s.licenses = ["MIT"]
@@ -85,44 +108,41 @@ Gem::Specification.new do |s|
85
108
  s.add_runtime_dependency(%q<activesupport>, [">= 0"])
86
109
  s.add_runtime_dependency(%q<activemodel>, [">= 0"])
87
110
  s.add_runtime_dependency(%q<json>, [">= 0"])
88
- s.add_development_dependency(%q<bundler>, ["~> 1.1.5"])
89
111
  s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
90
- s.add_development_dependency(%q<rcov>, [">= 0"])
91
112
  s.add_development_dependency(%q<reek>, ["~> 1.2.8"])
92
113
  s.add_development_dependency(%q<rest-client>, [">= 0"])
93
114
  s.add_development_dependency(%q<activesupport>, [">= 0"])
94
115
  s.add_development_dependency(%q<activemodel>, [">= 0"])
95
116
  s.add_development_dependency(%q<vcr>, [">= 0"])
96
117
  s.add_development_dependency(%q<webmock>, [">= 0"])
118
+ s.add_development_dependency(%q<turn>, [">= 0"])
97
119
  else
98
120
  s.add_dependency(%q<rest-client>, [">= 0"])
99
121
  s.add_dependency(%q<activesupport>, [">= 0"])
100
122
  s.add_dependency(%q<activemodel>, [">= 0"])
101
123
  s.add_dependency(%q<json>, [">= 0"])
102
- s.add_dependency(%q<bundler>, ["~> 1.1.5"])
103
124
  s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
104
- s.add_dependency(%q<rcov>, [">= 0"])
105
125
  s.add_dependency(%q<reek>, ["~> 1.2.8"])
106
126
  s.add_dependency(%q<rest-client>, [">= 0"])
107
127
  s.add_dependency(%q<activesupport>, [">= 0"])
108
128
  s.add_dependency(%q<activemodel>, [">= 0"])
109
129
  s.add_dependency(%q<vcr>, [">= 0"])
110
130
  s.add_dependency(%q<webmock>, [">= 0"])
131
+ s.add_dependency(%q<turn>, [">= 0"])
111
132
  end
112
133
  else
113
134
  s.add_dependency(%q<rest-client>, [">= 0"])
114
135
  s.add_dependency(%q<activesupport>, [">= 0"])
115
136
  s.add_dependency(%q<activemodel>, [">= 0"])
116
137
  s.add_dependency(%q<json>, [">= 0"])
117
- s.add_dependency(%q<bundler>, ["~> 1.1.5"])
118
138
  s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
119
- s.add_dependency(%q<rcov>, [">= 0"])
120
139
  s.add_dependency(%q<reek>, ["~> 1.2.8"])
121
140
  s.add_dependency(%q<rest-client>, [">= 0"])
122
141
  s.add_dependency(%q<activesupport>, [">= 0"])
123
142
  s.add_dependency(%q<activemodel>, [">= 0"])
124
143
  s.add_dependency(%q<vcr>, [">= 0"])
125
144
  s.add_dependency(%q<webmock>, [">= 0"])
145
+ s.add_dependency(%q<turn>, [">= 0"])
126
146
  end
127
147
  end
128
148