Parsistence 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :rubygems
2
+
3
+ # Specify your gem's dependencies in Parsistence.gemspec
4
+ gemspec
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/Parsistence/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "Parsistence"
6
+ s.version = Parsistence::VERSION
7
+ s.authors = ["Jamon Holmgren", "Silas J. Matson", "Alan deLevie"]
8
+ s.email = ["jamon@clearsightstudio.com", "silas@clearsightstudio.com", "adelevie@gmail.com"]
9
+ s.homepage = "https://github.com/clearsightstudio/Parsistence"
10
+ s.summary = %q{Your models on RubyMotion and Parse in a persistence.js style pattern.}
11
+ s.description = %q{Your models on RubyMotion and Parse in a persistence.js style pattern.}
12
+
13
+ s.rubyforge_project = "Parsistence"
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ # specify any dependencies here; for example:
21
+ # s.add_development_dependency "rspec"
22
+ # s.add_runtime_dependency "rest-client"
23
+ end
data/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # About
2
+
3
+ Parsistence provides an Active Record pattern to your Parse models on RubyMotion.
4
+ It's an early fork from [ParseModel](https://github.com/adelevie/ParseModel) by
5
+ Alan deLevie but goes a different direction with its implementation.
6
+
7
+ ## Usage
8
+
9
+ Create a model:
10
+
11
+ ```ruby
12
+ class Post
13
+ include Parsistence::Model
14
+
15
+ fields :title, :body, :author
16
+ end
17
+ ```
18
+
19
+ Create an instance:
20
+
21
+ ```ruby
22
+ p = Post.new
23
+ p.title = "Why RubyMotion Is Better Than Objective-C"
24
+ p.author = "Josh Symonds"
25
+ p.body = "trololol"
26
+ p.saveEventually
27
+ ```
28
+
29
+ `Parsistence::Model` objects will `respond_to?` to all methods available to [`PFObject`](https://parse.com/docs/ios/api/Classes/PFObject.html) in the Parse iOS SDK. You can also access the `PFObject` instance directly with, you guessed it, `Parsistence::Model#PFObject`.
30
+
31
+ ### Users
32
+
33
+ ```ruby
34
+ class User
35
+ include Parsistence::User
36
+ end
37
+
38
+ user = User.new
39
+ user.username = "adelevie"
40
+ user.email = "adelevie@gmail.com"
41
+ user.password = "foobar"
42
+ user.signUp
43
+
44
+ users = User.all
45
+ users.map {|u| u.objectId}.include?(user.objectId) #=> true
46
+ ```
47
+
48
+ `Parsistence::User` delegates to `PFUser` in a very similar fashion as `Parsistence::Model` delegates to `PFOBject`. `Parsistence::User` includes `Parsistence::Model`, in fact.
49
+
50
+ ### Queries
51
+
52
+ Queries use a somewhat different pattern than ActiveRecord but are relatively familiar. They are most like persistence.js.
53
+
54
+ ```ruby
55
+ Car.eq(license: "ABC-123", model: "Camry").order(year: :desc).limit(25).fetch do |cars, error|
56
+ if cars
57
+ cars.each do |car|
58
+ # `car` is an instance of your `Car` model here.
59
+ end
60
+ end
61
+ end
62
+ ```
63
+
64
+ Chain multiple conditions together, even the same condition type multiple times, then run `fetch` to execute the query. Pass in a block with two fields to receive the data.
65
+
66
+ ####Available Conditions
67
+ (note: each condition can take multiple comma-separated fields and values)
68
+
69
+ **eq:** Check if equal the passed in values.
70
+
71
+ **notEq:** Check if NOT equal to the passed in values.
72
+
73
+ **gt:** Check if greater than the passed in values.
74
+
75
+ **lt:** Check if less than the passed in values.
76
+
77
+ **gte:** Check if greater or equal to than the passed in values.
78
+
79
+ **lte:** Check if less than or equal to the passed in values.
80
+
81
+ **order:** Order by one or more fields. Specify :asc or :desc.
82
+
83
+ **limit:** Limit is slightly different...it takes either one argument (limit) or two (offset, limit).
84
+
85
+ ### Relationships
86
+
87
+ Define your relationships in the Parse.com dashboard and also in your models.
88
+
89
+ ```ruby
90
+ class Post
91
+ include Parsistence::Model
92
+
93
+ fields :title, :body, :author
94
+
95
+ relations :author
96
+ end
97
+
98
+ Author.where(name: "Jamon Holmgren").fetchOne do |fetchedAuthor, error|
99
+ p = Post.new
100
+ p.title = "Awesome Readme"
101
+ p.body = "Read this first!"
102
+ p.author = fetchedAuthor
103
+ p.save
104
+ end
105
+ ```
106
+
107
+
108
+ ## Installation
109
+
110
+ Either `gem install Parsistence` then `require 'Parsistence'` in your `Rakefile`, OR
111
+
112
+ `gem "Parsistence"` in your Gemfile. ([Instructions for Bundler setup with Rubymotion)](http://thunderboltlabs.com/posts/using-bundler-with-rubymotion)
113
+
114
+ Somewhere in your code, such as `app/app_delegate.rb` set your API keys:
115
+
116
+ ```ruby
117
+ Parse.setApplicationId("1234567890", clientKey:"abcdefghijk")
118
+ ```
119
+
120
+ To install the Parse iOS SDK in your RubyMotion project, read [this](http://www.rubymotion.com/developer-center/guides/project-management/#_using_3rd_party_libraries) and [this](http://stackoverflow.com/a/10453895/94154).
121
+
122
+ ## License
123
+
124
+ See LICENSE.txt
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ $:.unshift("/Library/RubyMotion/lib")
2
+ require 'motion/project'
3
+ require 'bundler'
4
+ Bundler.setup
@@ -0,0 +1,172 @@
1
+ module Parsistence
2
+ module Model
3
+ attr_accessor :PFObject, :errors
4
+
5
+ RESERVED_KEYS = [:objectId]
6
+
7
+ def initialize(pf=nil)
8
+ if pf
9
+ self.PFObject = pf
10
+ else
11
+ self.PFObject = PFObject.objectWithClassName(self.class.to_s)
12
+ end
13
+
14
+ # setupRelations unless pf
15
+
16
+ self
17
+ end
18
+
19
+ # This code is to correct for a bug where relations aren't initialized when creating a new instance
20
+ # def setupRelations
21
+ # relations.each do |r|
22
+ # self.send("#{r}=", @PFObject.relationforKey(r))
23
+ # end
24
+ # end
25
+
26
+ def method_missing(method, *args, &block)
27
+ method = method.to_sym
28
+ if fields.include?(method)
29
+ return getField(method)
30
+ elsif relations.include?(method)
31
+ return getRelation(method)
32
+ elsif relations.map {|r| "#{r}=".include?(method)}
33
+ method = method.split("=")[0]
34
+ return setRelation(method, args.first)
35
+ elsif @PFObject.respond_to?(method)
36
+ return @PFObject.send(method, *args, &block)
37
+ else
38
+ super
39
+ end
40
+ end
41
+
42
+ def fields
43
+ self.class.send(:get_fields)
44
+ end
45
+
46
+ def relations
47
+ self.class.send(:get_relations)
48
+ end
49
+
50
+ def getField(field)
51
+ field = field.to_sym
52
+ return @PFObject.send(field) if RESERVED_KEYS.include?(field)
53
+ return @PFObject[field] if fields.include? field
54
+ raise "Parsistence Exception: Invalid field name #{field} for object #{self.class.to_s}"
55
+ end
56
+
57
+ def setField(field, value)
58
+ return @PFObject.send("#{field}=", value) if RESERVED_KEYS.include?(field)
59
+ return @PFObject[field] = value if fields.include? field.to_sym
60
+ raise "Parsistence Exception: Invalid field name #{field} for object #{self.class.to_s}" unless fields.include? field.to_sym
61
+ end
62
+
63
+ def getRelation(field)
64
+ return @PFObject.objectForKey(field) if relations.include? field.to_sym
65
+ raise "Parsistence Exception: Invalid relation name #{field} for object #{self.class.to_s}"
66
+ end
67
+
68
+ def setRelation(relation, value)
69
+ value = value.PFObject if value.respond_to? :PFObject # unwrap object
70
+ # return setRelation(relation, value) # This SHOULD work
71
+
72
+ relation = @PFObject.relationforKey(relation)
73
+
74
+ return relation.addObject(value) if relations.include? relation.to_sym
75
+ raise "Parsistence Exception: Invalid relation name #{relation} for object #{self.class.to_s}" unless relations.include? relation.to_sym
76
+ end
77
+
78
+ def attributes
79
+ attributes = {}
80
+ fields.each do |f|
81
+ attributes[f] = getField(f)
82
+ end
83
+ @attributes = attributes
84
+ end
85
+
86
+ def attributes=(hashValue)
87
+ hashValue.each do |k, v|
88
+ next if v.nil? # Protection
89
+ setField(k, v) unless k.nil?
90
+ end
91
+ end
92
+
93
+ def save
94
+ saved = false
95
+ unless before_save == false
96
+ self.attributes.each do |field, value|
97
+ validateField field, value
98
+ end
99
+
100
+ saved = @PFObject.save
101
+ after_save if saved
102
+ end
103
+ saved
104
+ end
105
+
106
+ def before_save; end
107
+ def after_save; end
108
+
109
+ # Validations
110
+ def presenceValidations
111
+ self.class.send(:get_presenceValidations)
112
+ end
113
+
114
+ def validateField(field, value)
115
+ @errors ||= {}
116
+ @errors[field] = "#{field} can't be blank" if presenceValidations.include?(field) && value.nil? || value == ""
117
+ end
118
+
119
+ def errorForField(field)
120
+ @errors[field] || false
121
+ end
122
+
123
+
124
+ module ClassMethods
125
+ def fields(*args)
126
+ args.each {|arg| field(arg)}
127
+ end
128
+
129
+ def field(name)
130
+ @fields ||= [:objectId]
131
+ @fields << name.to_sym
132
+ @fields.uniq!
133
+ end
134
+
135
+ def get_fields
136
+ @fields
137
+ end
138
+
139
+ def relations(*args)
140
+ args.each { |arg| relation(arg)}
141
+ end
142
+
143
+ def relation(name)
144
+ @relations ||= []
145
+ @relations << name.to_sym
146
+ @relations.uniq!
147
+ end
148
+
149
+ def get_relations
150
+ @relations ||= []
151
+ end
152
+
153
+ def validates_presence_of(*args)
154
+ args.each {|arg| validate_presence(arg)}
155
+ end
156
+
157
+ def get_presenceValidations
158
+ @presenceValidations ||= []
159
+ end
160
+
161
+ def validate_presence(field)
162
+ @presenceValidations ||= []
163
+ @presenceValidations << field
164
+ end
165
+ end
166
+
167
+ def self.included(base)
168
+ base.extend(ClassMethods)
169
+ end
170
+
171
+ end
172
+ end
@@ -0,0 +1,223 @@
1
+ module Parsistence
2
+ module Model
3
+ module ClassMethods
4
+ QUERY_STUBS = [ :fetch, :where, :first, :limit, :order, :eq, :notEq, :lt, :gt, :lte, :gte ] # :limit is different
5
+
6
+ def method_missing(method, *args, &block)
7
+ if method == :limit
8
+ return self.limit(args.first) if args.length == 1
9
+ return self.limit(args.first, args.last)
10
+ elsif QUERY_STUBS.include? method.to_sym
11
+ q = Parsistence::Query.new
12
+ q.klass = self
13
+ return q.send(method, args.first, &block) if block
14
+ return q.send(method, args.first)
15
+ elsif method.start_with?("find_by_")
16
+ attribute = method.gsub("find_by_", "")
17
+ cond[attribute] = args.first
18
+ return self.limit(1).where(cond, block)
19
+ elsif method.start_with?("find_all_by_")
20
+ # attribute = method.gsub("find_all_by_", "")
21
+ # cond[attribute] = args.first
22
+ # return self.where(cond, block)
23
+ else
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ class Query
30
+ attr_accessor :klass
31
+
32
+ def initialize
33
+ @conditions = {}
34
+ @negativeConditions = {}
35
+ @ltConditions = {}
36
+ @gtConditions = {}
37
+ @lteConditions = {}
38
+ @gteConditions = {}
39
+ @order = {}
40
+ @limit = nil
41
+ @offset = nil
42
+ @includes = []
43
+ end
44
+
45
+ def createQuery
46
+ query = PFQuery.queryWithClassName(self.klass.to_s)
47
+ $stderr.puts @includes
48
+ @includes.each do |include|
49
+ query.includeKey(include)
50
+ end
51
+
52
+ @conditions.each do |key, value|
53
+ value = value.PFObject if value.respond_to? :PFObject
54
+ query.whereKey(key, equalTo: value)
55
+ end
56
+ @negativeConditions.each do |key, value|
57
+ value = value.PFObject if value.respond_to? :PFObject
58
+ query.whereKey(key, notEqualTo: value)
59
+ end
60
+ @ltConditions.each do |key, value|
61
+ value = value.PFObject if value.respond_to? :PFObject
62
+ query.whereKey(key, lessThan: value)
63
+ end
64
+ @gtConditions.each do |key, value|
65
+ value = value.PFObject if value.respond_to? :PFObject
66
+ query.whereKey(key, greaterThan: value)
67
+ end
68
+ @lteConditions.each do |key, value|
69
+ value = value.PFObject if value.respond_to? :PFObject
70
+ query.whereKey(key, lessThanOrEqualTo: value)
71
+ end
72
+ @gteConditions.each do |key, value|
73
+ value = value.PFObject if value.respond_to? :PFObject
74
+ query.whereKey(key, greaterThanOrEqualTo: value)
75
+ end
76
+ first = true
77
+ @order.each do |field, direction|
78
+ if first
79
+ # $stderr.puts "Setting order first"
80
+ query.orderByAscending(field) if direction && direction == :asc
81
+ query.orderByDescending(field) if direction && direction == :desc
82
+ first = false
83
+ else
84
+ # $stderr.puts "Setting order again"
85
+ query.addAscendingOrder(field) if direction && direction == :asc
86
+ query.addDescendingOrder(field) if direction && direction == :desc
87
+ end
88
+ end
89
+
90
+ query.limit = @limit if @limit
91
+ query.skip = @offset if @offset
92
+
93
+ query
94
+ end
95
+
96
+ def fetch(&callback)
97
+ if @limit && @limit == 1
98
+ fetchOne(&callback)
99
+ else
100
+ fetchAll(&callback)
101
+ end
102
+
103
+ self
104
+ end
105
+
106
+ def fetchAll(&callback)
107
+ query = createQuery
108
+
109
+ myKlass = self.klass
110
+ query.findObjectsInBackgroundWithBlock (lambda { |items, error|
111
+ modelItems = items.map! { |item| myKlass.new(item) } if items
112
+ callback.call modelItems, error
113
+ })
114
+ end
115
+
116
+ def fetchOne(&callback)
117
+ limit(0, 1)
118
+ query = createQuery
119
+
120
+ myKlass = self.klass
121
+ query.getFirstObjectInBackgroundWithBlock (lambda { |item, error|
122
+ modelItem = myKlass.new(item) if item
123
+ callback.call modelItem, error
124
+ })
125
+ end
126
+
127
+ # Query methods
128
+ def where(*conditions, &callback)
129
+ eq(conditions.first)
130
+ fetch(&callback)
131
+ nil
132
+ end
133
+
134
+ def all(&callback)
135
+ fetch(&callback)
136
+ nil
137
+ end
138
+
139
+ def first(&callback)
140
+ limit(0, 1)
141
+ fetch(&callback)
142
+ nil
143
+ end
144
+
145
+ def showQuery
146
+ $stderr.puts "Conditions: #{@conditions.to_s}"
147
+ $stderr.puts "negativeConditions: #{@negativeConditions.to_s}"
148
+ $stderr.puts "ltConditions: #{@ltConditions.to_s}"
149
+ $stderr.puts "gtConditions: #{@gtConditions.to_s}"
150
+ $stderr.puts "lteConditions: #{@lteConditions.to_s}"
151
+ $stderr.puts "gteConditions: #{@gteConditions.to_s}"
152
+ $stderr.puts "order: #{@order.to_s}"
153
+ $stderr.puts "limit: #{@limit.to_s}"
154
+ $stderr.puts "offset: #{@offset.to_s}"
155
+ end
156
+
157
+ # Query parameter methods
158
+ def limit(offset, number = nil)
159
+ if number.nil?
160
+ number = offset
161
+ offset = 0
162
+ end
163
+ @offset = offset
164
+ @limit = number
165
+ self
166
+ end
167
+
168
+ def order(*fields)
169
+ fields.each do |field|
170
+ @order.merge! field
171
+ end
172
+ self
173
+ end
174
+
175
+ def eq(*fields)
176
+ fields.each do |field|
177
+ @conditions.merge! field
178
+ end
179
+ self
180
+ end
181
+
182
+ def notEq(*fields)
183
+ fields.each do |field|
184
+ @negativeConditions.merge! field
185
+ end
186
+ self
187
+ end
188
+
189
+ def lt(*fields)
190
+ fields.each do |field|
191
+ @ltConditions.merge! field
192
+ end
193
+ self
194
+ end
195
+
196
+ def gt(*fields)
197
+ fields.each do |field|
198
+ @gtConditions.merge! field
199
+ end
200
+ self
201
+ end
202
+
203
+ def lte(*fields)
204
+ fields.each do |field|
205
+ @lteConditions.merge! field
206
+ end
207
+ self
208
+ end
209
+
210
+ def gte(*fields)
211
+ fields.each do |field|
212
+ @gteConditions.merge! field
213
+ end
214
+ self
215
+ end
216
+
217
+ def includes(*fields)
218
+ fields.each do |field|
219
+ @includes << field
220
+ end
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,34 @@
1
+ module Parsistence
2
+ module User
3
+ include ::Parsistence::Model
4
+
5
+ attr_accessor :PFUser
6
+
7
+ RESERVED_KEYS = [:objectId, :username, :password, :email]
8
+
9
+ def PFObject=(value)
10
+ @PFObject = value
11
+ @PFUser = @PFObject
12
+ end
13
+
14
+ def PFUser=(value)
15
+ self.PFObject = value
16
+ end
17
+
18
+ module ClassMethods
19
+ def all
20
+ query = PFQuery.queryForUser
21
+ users = query.findObjects
22
+ users
23
+ end
24
+
25
+ def currentUser
26
+ PFUser.currentUser
27
+ end
28
+ end
29
+
30
+ def self.included(base)
31
+ base.extend(ClassMethods)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ module Parsistence
2
+ VERSION = "0.2.0"
3
+ end
@@ -0,0 +1,8 @@
1
+ require "Parsistence/version"
2
+
3
+ Motion::Project::App.setup do |app|
4
+ Dir.glob(File.join(File.dirname(__FILE__), "Parsistence/*.rb")).each do |file|
5
+ app.files.unshift(file) unless file.include? "Model.rb"
6
+ end
7
+ app.files.unshift(File.join(File.dirname(__FILE__), "Parsistence/Model.rb"))
8
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: Parsistence
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jamon Holmgren
9
+ - Silas J. Matson
10
+ - Alan deLevie
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+ date: 2012-09-05 00:00:00.000000000 Z
15
+ dependencies: []
16
+ description: Your models on RubyMotion and Parse in a persistence.js style pattern.
17
+ email:
18
+ - jamon@clearsightstudio.com
19
+ - silas@clearsightstudio.com
20
+ - adelevie@gmail.com
21
+ executables: []
22
+ extensions: []
23
+ extra_rdoc_files: []
24
+ files:
25
+ - .gitignore
26
+ - Gemfile
27
+ - Parsistence.gemspec
28
+ - README.md
29
+ - Rakefile
30
+ - lib/Parsistence.rb
31
+ - lib/Parsistence/Model.rb
32
+ - lib/Parsistence/Query.rb
33
+ - lib/Parsistence/User.rb
34
+ - lib/Parsistence/version.rb
35
+ homepage: https://github.com/clearsightstudio/Parsistence
36
+ licenses: []
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubyforge_project: Parsistence
55
+ rubygems_version: 1.8.22
56
+ signing_key:
57
+ specification_version: 3
58
+ summary: Your models on RubyMotion and Parse in a persistence.js style pattern.
59
+ test_files: []
60
+ has_rdoc: