parse_resource 1.5.11 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,16 +1,42 @@
1
1
  ParseResource
2
2
  =============
3
3
 
4
- ParseResource makes it easy to interact with Parse.com's REST API. It adheres to the ActiveRecord pattern. ParceResource is fully ActiveModel compliant, meaning you can use validations, callbacks, and Rails forms.
4
+ ParseResource makes it easy to interact with Parse.com's REST API. It adheres to the ActiveRecord pattern. ParceResource is fully ActiveModel compliant, meaning you can use validations and Rails forms.
5
5
 
6
6
  Ruby/Rails developers should feel right at home.
7
7
 
8
+ Features
9
+ ---------------
10
+ * ActiveRecord-like API, almost no learning curve
11
+ * Validations
12
+ * Rails forms and scaffolds **just work**
13
+
14
+ Use cases
15
+ -------------
16
+ * Build a custom admin dashboard for your Parse.com data
17
+ * Use the same database for your web and native apps
18
+ * Pre-collect data for use in iOS and Android apps
19
+
20
+ To-do
21
+ --------------
22
+ * User authentication
23
+ * Better documentation
24
+ * Associations
25
+ * Callbacks
26
+ * Push notifications
27
+ * Better type-casting
28
+
29
+ User authentication is my top priority feature. Several people have specifically requested it, and Parse just began exposing [`User` objects in the REST API](https://www.parse.com/docs/rest#users).
30
+
31
+ Let me know of any other features you want.
32
+
33
+
8
34
  Words of caution
9
35
  ---------------
10
36
 
11
- ParseResource is brand new and has no test coverage. You read that right. I figured I'd ship now and write tests later. We'll see how it goes.
37
+ ParseResource is brand new. Test coverage is decent.
12
38
 
13
- This is also my first gem. Be afraid.
39
+ This is my first gem. Be afraid.
14
40
 
15
41
  Installation
16
42
  ------------
@@ -18,7 +44,7 @@ Installation
18
44
  Include in your `Gemfile`:
19
45
 
20
46
  ```ruby
21
- gem "parse_resource"
47
+ gem "parse_resource", "~> 1.5.11"
22
48
  ```
23
49
 
24
50
  Or just gem install:
@@ -59,13 +85,15 @@ Usage
59
85
  Create a model:
60
86
 
61
87
  ```ruby
62
- class Post < ParseResource
88
+ class Post < ParseResource::Base
63
89
  fields :title, :author, :body
64
90
 
65
91
  validates_presence_of :title
66
92
  end
67
93
  ```
68
94
 
95
+ If you are using version `1.5.11` or earlier, subclass to just `ParseResource`.
96
+
69
97
  Creating, updating, and deleting:
70
98
 
71
99
  ```ruby
@@ -124,13 +152,14 @@ p = Post.find(id)
124
152
 
125
153
  Contributing to ParseResource
126
154
  -----------------------------
127
-
155
+
128
156
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
129
157
  * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
130
158
  * Fork the project
131
159
  * Start a feature/bugfix branch
132
160
  * Commit and push until you are happy with your contribution
133
- * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally. (A little hypocritical since I haven't written tests yet)
161
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
162
+ * Create `parse_resource.yml` in the root of the gem folder. Using the same format as `parse_resource.yml` in the instructions (except only creating a `test` environment, add your own API keys.
134
163
  * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
135
164
 
136
165
  Copyright
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.5.11
1
+ 1.6.0
@@ -0,0 +1,289 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+ require "active_model"
4
+ require "erb"
5
+ require "rest-client"
6
+ require "json"
7
+ require "active_support/hash_with_indifferent_access"
8
+
9
+ module ParseResource
10
+
11
+ class Base
12
+ # ParseResource::Base provides an easy way to use Ruby to interace with a Parse.com backend
13
+ # Usage:
14
+ # class Post < ParseResource::Base
15
+ # fields :title, :author, :body
16
+ # end
17
+
18
+ include ActiveModel::Validations
19
+ include ActiveModel::Conversion
20
+ include ActiveModel::AttributeMethods
21
+ extend ActiveModel::Naming
22
+ extend ActiveModel::Callbacks
23
+ HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
24
+
25
+ # define_model_callbacks :initialize, :find, :only => :after
26
+ define_model_callbacks :save, :create, :update, :destroy
27
+
28
+
29
+ # Instantiates a ParseResource::Base object
30
+ #
31
+ # @params [Hash], [Boolean] a `Hash` of attributes and a `Boolean` that should be false only if the object already exists
32
+ # @return [ParseResource::Base] an object that subclasses `Parseresource::Base`
33
+ def initialize(attributes = {}, new=true)
34
+ attributes = HashWithIndifferentAccess.new(attributes)
35
+ if new
36
+ @unsaved_attributes = attributes
37
+ else
38
+ @unsaved_attributes = {}
39
+ end
40
+ self.attributes = {}
41
+ self.attributes.merge!(attributes)
42
+ self.attributes unless self.attributes.empty?
43
+ create_setters!
44
+ end
45
+
46
+ # Explicitly adds a field to the model.
47
+ #
48
+ # @param [Symbol] name the name of the field, eg `:author`.
49
+ # @param [Boolean] val the return value of the field. Only use this within the class.
50
+ def self.field(name, val=nil)
51
+ class_eval do
52
+ define_method(name) do
53
+ @attributes[name] ? @attributes[name] : @unsaved_attributes[name]
54
+ end
55
+ define_method("#{name}=") do |val|
56
+ @attributes[name] = val
57
+ @unsaved_attributes[name] = val
58
+ val
59
+ end
60
+ end
61
+ end
62
+
63
+ # Add multiple fields in one line. Same as `#field`, but accepts multiple args.
64
+ #
65
+ # @param [Array] *args an array of `Symbol`s, `eg :author, :body, :title`.
66
+ def self.fields(*args)
67
+ args.each {|f| field(f)}
68
+ end
69
+
70
+ # Creates getter and setter methods for model fields
71
+ #
72
+ def create_setters!
73
+ @attributes.each_pair do |k,v|
74
+ self.class.send(:define_method, "#{k}=") do |val|
75
+ if k.is_a?(Symbol)
76
+ k = k.to_s
77
+ end
78
+ @attributes[k.to_s] = val
79
+ @unsaved_attributes[k.to_s] = val
80
+ val
81
+ end
82
+ self.class.send(:define_method, "#{k}") do
83
+ if k.is_a?(Symbol)
84
+ k = k.to_s
85
+ end
86
+
87
+ @attributes[k.to_s]
88
+ end
89
+ end
90
+ end
91
+
92
+ class << self
93
+ def has_one(child_name)
94
+ class_eval do
95
+
96
+ define_method("#{child_name}") do
97
+ child_name
98
+ end
99
+
100
+ define_method("#{child_name}=") do |child_object|
101
+ [child_object, child_name]
102
+ end
103
+
104
+ end
105
+ end
106
+
107
+ def belongs_to(name)
108
+ class_eval do
109
+
110
+ define_method("#{parent_name}") do
111
+ name
112
+ end
113
+
114
+ define_method("#{parent_name}=") do |parent_object|
115
+ [parent_name, parent_object]
116
+ end
117
+
118
+ end
119
+ end
120
+
121
+
122
+ @@settings ||= nil
123
+
124
+ # Explicitly set Parse.com API keys.
125
+ #
126
+ # @param [String] app_id the Application ID of your Parse database
127
+ # @param [String] master_key the Master Key of your Parse database
128
+ def load!(app_id, master_key)
129
+ @@settings = {"app_id" => app_id, "master_key" => master_key}
130
+ end
131
+
132
+ # Creates a RESTful resource
133
+ # sends requests to [base_uri]/[classname]
134
+ #
135
+ def resource
136
+ if @@settings.nil?
137
+ path = "config/parse_resource.yml"
138
+ environment = defined?(Rails) && Rails.respond_to?(:env) ? Rails.env : ENV["RACK_ENV"]
139
+ @@settings = YAML.load(ERB.new(File.new(path).read).result)[environment]
140
+ end
141
+ base_uri = "https://api.parse.com/1/classes/#{model_name}"
142
+ app_id = @@settings['app_id']
143
+ master_key = @@settings['master_key']
144
+ RestClient::Resource.new(base_uri, app_id, master_key)
145
+ end
146
+
147
+ # Find a ParseResource::Baseobject by ID
148
+ #
149
+ # @param [String] id the ID of the Parse object you want to find.
150
+ # @return [ParseResource] an object that subclasses ParseResource.
151
+ def find(id)
152
+ where(:objectId => id).first
153
+ end
154
+
155
+ # Find a ParseResource::Baseobject by a `Hash` of conditions.
156
+ #
157
+ # @param [Hash] parameters a `Hash` of conditions.
158
+ # @return [Array] an `Array` of objects that subclass `ParseResource`.
159
+ def where(parameters)
160
+ resp = resource.get(:params => {:where => parameters.to_json})
161
+ results = JSON.parse(resp)['results']
162
+ results.map {|r| model_name.constantize.new(r, false)}
163
+ end
164
+
165
+ # Find all ParseResource::Baseobjects for that model.
166
+ #
167
+ # @return [Array] an `Array` of objects that subclass `ParseResource`.
168
+ def all
169
+ resp = resource.get
170
+ results = JSON.parse(resp)['results']
171
+ results.map {|r| model_name.constantize.new(r, false)}
172
+ end
173
+
174
+ # Create a ParseResource::Baseobject.
175
+ #
176
+ # @param [Hash] attributes a `Hash` of attributes
177
+ # @return [ParseResource] an object that subclasses `ParseResource`. Or returns `false` if object fails to save.
178
+ def create(attributes = {})
179
+ attributes = HashWithIndifferentAccess.new(attributes)
180
+ new(attributes).save
181
+ end
182
+
183
+ # Find the first object. Fairly random, not based on any specific condition.
184
+ #
185
+ def first
186
+ all.first
187
+ end
188
+
189
+ def class_attributes
190
+ @class_attributes ||= {}
191
+ end
192
+
193
+ end
194
+
195
+ def persisted?
196
+ if id
197
+ true
198
+ else
199
+ false
200
+ end
201
+ end
202
+
203
+ def new?
204
+ !persisted?
205
+ end
206
+
207
+ # delegate from Class method
208
+ def resource
209
+ self.class.resource
210
+ end
211
+
212
+ # create RESTful resource for the specific Parse object
213
+ # sends requests to [base_uri]/[classname]/[objectId]
214
+ def instance_resource
215
+ self.class.resource["#{self.id}"]
216
+ end
217
+
218
+ def create
219
+ resp = self.resource.post(@unsaved_attributes.to_json, :content_type => "application/json")
220
+ @attributes.merge!(JSON.parse(resp))
221
+ @attributes.merge!(@unsaved_attributes)
222
+ attributes = HashWithIndifferentAccess.new(attributes)
223
+ @unsaved_attributes = {}
224
+ create_setters!
225
+ self
226
+ end
227
+
228
+ def save
229
+ if valid?
230
+ run_callbacks :save do
231
+ new? ? create : update
232
+ end
233
+ else
234
+ false
235
+ end
236
+ rescue false
237
+ end
238
+
239
+ def update(attributes = {})
240
+ attributes = HashWithIndifferentAccess.new(attributes)
241
+ @unsaved_attributes.merge!(attributes)
242
+
243
+ put_attrs = @unsaved_attributes
244
+ put_attrs.delete('objectId')
245
+ put_attrs.delete('createdAt')
246
+ put_attrs.delete('updatedAt')
247
+ put_attrs = put_attrs.to_json
248
+
249
+ resp = self.instance_resource.put(put_attrs, :content_type => "application/json")
250
+
251
+ @attributes.merge!(JSON.parse(resp))
252
+ @attributes.merge!(@unsaved_attributes)
253
+ @unsaved_attributes = {}
254
+ create_setters!
255
+
256
+ self
257
+ end
258
+
259
+ def update_attributes(attributes = {})
260
+ self.update(attributes)
261
+ end
262
+
263
+ def destroy
264
+ self.instance_resource.delete
265
+ @attributes = {}
266
+ @unsaved_attributes = {}
267
+ nil
268
+ end
269
+
270
+ # provides access to @attributes for getting and setting
271
+ def attributes
272
+ @attributes ||= self.class.class_attributes
273
+ @attributes
274
+ end
275
+
276
+ def attributes=(n)
277
+ @attributes = n
278
+ @attributes
279
+ end
280
+
281
+ # aliasing for idiomatic Ruby
282
+ def id; self.objectId rescue nil; end
283
+
284
+ def created_at; self.createdAt; end
285
+
286
+ def updated_at; self.updatedAt rescue nil; end
287
+
288
+ end
289
+ end
@@ -1,15 +1,11 @@
1
+ require 'helper'
1
2
  require 'parse_resource'
2
- require 'pp'
3
3
 
4
+ path = "parse_resource.yml"
5
+ settings = YAML.load(ERB.new(File.new(path).read).result)['test']
4
6
 
5
- ParseResource.load!("FKEzdzDgEyghLDFgIVHYJehVlWpfVtUmEv4MUEkJ", "bOYO7usWbrcIbL5L5bPzlYrSonQRvwJecC1XLsuN")
7
+ ParseResource.load!(settings['app_id'], settings['master_key'])
6
8
 
7
9
  class Post < ParseResource
8
10
  fields :title, :author, :body
9
-
10
- after_save :add_author
11
-
12
- def add_author
13
- update(:author => "Alan")
14
- end
15
11
  end
@@ -1,244 +1,4 @@
1
- require "rubygems"
2
- require "bundler/setup"
3
- require "active_model"
4
- require "erb"
5
- require "rest-client"
6
- require "json"
7
- require "active_support/hash_with_indifferent_access"
1
+ require 'base'
8
2
 
9
- ActiveSupport
10
-
11
- class ParseResource
12
- # ParseResource provides an easy way to use Ruby to interace with a Parse.com backend
13
- # Usage:
14
- # class Post < ParseResource
15
- # fields :title, :author, :body
16
- # end
17
-
18
- include ActiveModel::Validations
19
- include ActiveModel::Conversion
20
- include ActiveModel::AttributeMethods
21
- extend ActiveModel::Naming
22
- extend ActiveModel::Callbacks
23
- HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
24
-
25
- #define_model_callbacks :initialize, :find, :only => :after
26
- define_model_callbacks :save, :create, :update, :destroy
27
-
28
- # instantiation!
29
- # p = Post.new(:title => "cool story")
30
- def initialize(attributes = {}, new=true)
31
- attributes = HashWithIndifferentAccess.new(attributes)
32
- if new
33
- @unsaved_attributes = attributes
34
- else
35
- @unsaved_attributes = {}
36
- end
37
- self.attributes = {}
38
- self.attributes.merge!(attributes)
39
- self.attributes unless self.attributes.empty?
40
- create_setters!
41
- end
42
-
43
- def self.field(name, val=nil)
44
- class_eval do
45
- define_method(name) do
46
- @attributes[name] ? @attributes[name] : @unsaved_attributes[name]
47
- end
48
- define_method("#{name}=") do |val|
49
- @attributes[name] = val
50
- @unsaved_attributes[name] = val
51
- val
52
- end
53
- end
54
- end
55
-
56
- def self.fields(*args)
57
- args.each {|f| field(f)}
58
- end
59
-
60
- # a sprinkle of metaprogramming
61
- # p = Post.new(:some_attr => "some value")
62
- # p.some_attr = "new value"
63
- def create_setters!
64
- @attributes.each_pair do |k,v|
65
- self.class.send(:define_method, "#{k}=") do |val|
66
- if k.is_a?(Symbol)
67
- k = k.to_s
68
- end
69
- @attributes[k.to_s] = val
70
- @unsaved_attributes[k.to_s] = val
71
- val
72
- end
73
- self.class.send(:define_method, "#{k}") do
74
- if k.is_a?(Symbol)
75
- k = k.to_s
76
- end
77
-
78
- @attributes[k.to_s]
79
- end
80
- end
81
- end
82
-
83
- class << self
84
- @@settings ||= nil
85
-
86
- def load!(app_id, master_key)
87
- @@settings = {"app_id" => app_id, "master_key" => master_key}
88
- end
89
-
90
- # creates a RESTful resource
91
- # sends requests to [base_uri]/[classname]
92
- def resource
93
- if @@settings.nil?
94
- path = "config/parse_resource.yml"
95
- environment = defined?(Rails) && Rails.respond_to?(:env) ? Rails.env : ENV["RACK_ENV"]
96
- @@settings = YAML.load(ERB.new(File.new(path).read).result)[environment]
97
- end
98
- base_uri = "https://api.parse.com/1/classes/#{model_name}"
99
- app_id = @@settings['app_id']
100
- master_key = @@settings['master_key']
101
- RestClient::Resource.new(base_uri, app_id, master_key)
102
- end
103
-
104
- # finders
105
- # Post.find("abcdf")
106
- def find(id)
107
- where(:objectId => id).first
108
- end
109
-
110
- # Post.where(:author => "Alan", :title => "Ipso Lorem")
111
- def where(parameters)
112
- resp = resource.get(:params => {:where => parameters.to_json})
113
- results = JSON.parse(resp)['results']
114
- results.map {|r| model_name.constantize.new(r, false)}
115
- end
116
-
117
- # Post.all
118
- def all
119
- resp = resource.get
120
- results = JSON.parse(resp)['results']
121
- results.map {|r| model_name.constantize.new(r, false)}
122
- end
123
-
124
- # Post.create(:title => "new post")
125
- def create(attributes = {})
126
- attributes = HashWithIndifferentAccess.new(attributes)
127
- new(attributes).save
128
- end
129
-
130
- # Post.first
131
- def first
132
- all.first
133
- end
134
-
135
- def class_attributes
136
- @class_attributes ||= {}
137
- end
138
-
139
- end
140
-
141
- def persisted?
142
- if id
143
- true
144
- else
145
- false
146
- end
147
- end
148
-
149
- def new?
150
- !persisted?
151
- end
152
-
153
- # delegate from Class method
154
- def resource
155
- self.class.resource
156
- end
157
-
158
- # create RESTful resource for the specific Parse object
159
- # sends requests to [base_uri]/[classname]/[objectId]
160
- def instance_resource
161
- self.class.resource["#{self.id}"]
162
- end
163
-
164
- def create
165
- resp = self.resource.post(@unsaved_attributes.to_json, :content_type => "application/json")
166
- @attributes.merge!(JSON.parse(resp))
167
- @attributes.merge!(@unsaved_attributes)
168
- attributes = HashWithIndifferentAccess.new(attributes)
169
- @unsaved_attributes = {}
170
- create_setters!
171
- self
172
- end
173
-
174
- def save
175
- if valid?
176
- run_callbacks :save do
177
- new? ? create : update
178
- end
179
- else
180
- false
181
- end
182
- rescue false
183
- end
184
-
185
- def update(attributes = {})
186
- attributes = HashWithIndifferentAccess.new(attributes)
187
- @unsaved_attributes.merge!(attributes)
188
-
189
- put_attrs = @unsaved_attributes
190
- put_attrs.delete('objectId')
191
- put_attrs.delete('createdAt')
192
- put_attrs.delete('updatedAt')
193
- put_attrs = put_attrs.to_json
194
-
195
- resp = self.instance_resource.put(put_attrs, :content_type => "application/json")
196
-
197
- @attributes.merge!(JSON.parse(resp))
198
- @attributes.merge!(@unsaved_attributes)
199
- @unsaved_attributes = {}
200
- create_setters!
201
-
202
- self
203
- end
204
-
205
- def update_attributes(attributes = {})
206
- self.update(attributes)
207
- end
208
-
209
- def destroy
210
- self.instance_resource.delete
211
- @attributes = {}
212
- @unsaved_attributes = {}
213
- nil
214
- end
215
-
216
- # provides access to @attributes for getting and setting
217
- def attributes
218
- @attributes ||= self.class.class_attributes
219
- @attributes
220
- end
221
-
222
- def attributes=(n)
223
- @attributes = n
224
- @attributes
225
- end
226
-
227
- # aliasing for idiomatic Ruby
228
- def id; self.objectId rescue nil; end
229
-
230
- def created_at; self.createdAt; end
231
-
232
- def updated_at; self.updatedAt rescue nil; end
233
-
234
- # another sprinkle of metaprogramming
235
- # p = Post.new(:some_attr => "some value")
236
- # p.some_attr #=> "some value"
237
- #def method_missing(meth, *args, &block)
238
- # if @attributes.has_key?(meth.to_s)
239
- # @attributes[meth.to_s]
240
- # else
241
- # super
242
- # end
243
- #end
3
+ module ParseResource
244
4
  end