Parsistence 0.2.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.
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: