motion-prime 0.1.0 → 0.1.1
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 +2 -1
- data/.travis.yml +6 -0
- data/Gemfile +5 -10
- data/Gemfile.lock +10 -7
- data/README.md +15 -7
- data/Rakefile +3 -2
- data/files/Gemfile +8 -1
- data/files/Rakefile +2 -0
- data/files/app/app_delegate.rb +10 -0
- data/files/app/config/base.rb +6 -0
- data/files/app/models/.gitkeep +0 -0
- data/files/app/screens/application_screen.rb +4 -0
- data/files/app/screens/help_screen.rb +5 -0
- data/files/app/screens/home_screen.rb +5 -0
- data/files/app/screens/sidebar_screen.rb +14 -0
- data/files/app/sections/sidebar/action.rb +5 -0
- data/files/app/sections/sidebar/table.rb +20 -0
- data/files/app/styles/sidebar.rb +38 -0
- data/files/resources/images/navigation/bg.png +0 -0
- data/files/resources/images/navigation/bg@2x.png +0 -0
- data/files/resources/images/navigation/button.png +0 -0
- data/files/resources/images/navigation/button@2x.png +0 -0
- data/lib/motion-prime.rb +1 -1
- data/motion-prime.gemspec +3 -0
- data/motion-prime/config/base.rb +8 -0
- data/motion-prime/config/config.rb +49 -0
- data/motion-prime/elements/draw/label.rb +1 -1
- data/motion-prime/models/association.rb +115 -0
- data/motion-prime/models/bag.rb +129 -0
- data/motion-prime/models/base.rb +15 -65
- data/motion-prime/models/errors.rb +3 -0
- data/motion-prime/models/finder.rb +189 -0
- data/motion-prime/models/json.rb +45 -0
- data/motion-prime/models/model.rb +118 -0
- data/motion-prime/models/store.rb +53 -0
- data/motion-prime/models/store_extension.rb +159 -0
- data/motion-prime/styles/{forms.rb → base.rb} +4 -0
- data/motion-prime/support/dm_view_controller.rb +1 -1
- data/motion-prime/version.rb +1 -1
- data/spec/config/store_spec.rb +73 -0
- data/spec/delegate/base_delegate_spec.rb +12 -0
- data/spec/helpers/base_delegate.rb +5 -0
- data/spec/helpers/base_screen.rb +9 -0
- data/spec/helpers/models.rb +53 -0
- data/spec/models/association_spec.rb +49 -0
- data/spec/models/bag_spec.rb +92 -0
- data/spec/models/base_model_spec.rb +158 -0
- data/spec/models/finder_spec.rb +183 -0
- data/spec/models/store_extension_spec.rb +120 -0
- data/spec/models/store_spec.rb +51 -0
- data/spec/screens/base_screen_spec.rb +77 -0
- metadata +84 -8
- data/lib/view_styler.rb +0 -141
- data/spec/main_spec.rb +0 -9
@@ -0,0 +1,129 @@
|
|
1
|
+
module MotionPrime
|
2
|
+
module BagInstanceMethods
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
alias_method :saved, :savedObjects
|
6
|
+
alias_method :unsaved, :unsavedObjects
|
7
|
+
alias_method :removed, :removedObjects
|
8
|
+
alias_method :size, :count
|
9
|
+
alias_method :inflate, :inflateBag
|
10
|
+
alias_method :deflate, :deflateBag
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def originalClassString
|
15
|
+
'NSFNanoBag'
|
16
|
+
end
|
17
|
+
|
18
|
+
def changed?
|
19
|
+
self.hasUnsavedChanges
|
20
|
+
end
|
21
|
+
|
22
|
+
# Return all objects in bag
|
23
|
+
#
|
24
|
+
# @return Array
|
25
|
+
def to_a
|
26
|
+
self.savedObjects.values + self.unsavedObjects.values
|
27
|
+
end
|
28
|
+
|
29
|
+
# Add an object to bag
|
30
|
+
#
|
31
|
+
# @return self
|
32
|
+
def <<(object)
|
33
|
+
error_ptr = Pointer.new(:id)
|
34
|
+
self.addObject(object, error:error_ptr)
|
35
|
+
raise StoreError, error_ptr[0].description if error_ptr[0]
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
# Add an object or array of objects to bag
|
40
|
+
#
|
41
|
+
# @return self
|
42
|
+
def add(object_or_array)
|
43
|
+
error_ptr = Pointer.new(:id)
|
44
|
+
if object_or_array.is_a?(Array)
|
45
|
+
self.addObjectsFromArray(object_or_array, error:error_ptr)
|
46
|
+
else
|
47
|
+
self.addObject(object_or_array, error:error_ptr)
|
48
|
+
end
|
49
|
+
raise StoreError, error_ptr[0].description if error_ptr[0]
|
50
|
+
self
|
51
|
+
end
|
52
|
+
alias_method :+, :add
|
53
|
+
|
54
|
+
# Remove object from bag with key
|
55
|
+
#
|
56
|
+
# @param [String] key - a key or array of keys
|
57
|
+
# @return self
|
58
|
+
def delete_key(key)
|
59
|
+
if key.is_a?(Array)
|
60
|
+
self.removeObjectsWithKeysInArray(key)
|
61
|
+
else
|
62
|
+
self.removeObjectWithKey(key)
|
63
|
+
end
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
# Remove an object or array of objects from bag
|
68
|
+
#
|
69
|
+
# @param [String] object_or_array
|
70
|
+
# @return self
|
71
|
+
def delete(object_or_array)
|
72
|
+
error_ptr = Pointer.new(:id)
|
73
|
+
if object_or_array.is_a?(Array)
|
74
|
+
self.removeObjectsInArray(object_or_array, error: error_ptr)
|
75
|
+
else
|
76
|
+
self.removeObject(object_or_array, error_ptr)
|
77
|
+
end
|
78
|
+
raise StoreError, error_ptr[0].description if error_ptr[0]
|
79
|
+
self
|
80
|
+
end
|
81
|
+
alias_method :-, :delete
|
82
|
+
|
83
|
+
# Clear content of the bag
|
84
|
+
#
|
85
|
+
# @return self
|
86
|
+
def delete_all
|
87
|
+
bag_copy = to_a.clone
|
88
|
+
# this removes childrens from model
|
89
|
+
delete(self.to_a)
|
90
|
+
# this removes collection from db
|
91
|
+
bag_copy.each(&:delete)
|
92
|
+
self
|
93
|
+
end
|
94
|
+
alias_method :clear, :delete_all
|
95
|
+
|
96
|
+
|
97
|
+
def store=(store)
|
98
|
+
store.addObject(self, error:nil)
|
99
|
+
end
|
100
|
+
|
101
|
+
def save
|
102
|
+
self.store ||= MotionPrime::Store.shared_store
|
103
|
+
error_ptr = Pointer.new(:id)
|
104
|
+
result = self.saveAndReturnError(error_ptr)
|
105
|
+
raise StoreError, error_ptr[0].description if error_ptr[0]
|
106
|
+
result
|
107
|
+
end
|
108
|
+
|
109
|
+
def reload
|
110
|
+
error_ptr = Pointer.new(:id)
|
111
|
+
result = self.reloadBagWithError(error_ptr)
|
112
|
+
raise StoreError, error_ptr[0].description if error_ptr[0]
|
113
|
+
result
|
114
|
+
end
|
115
|
+
|
116
|
+
def undo
|
117
|
+
error_ptr = Pointer.new(:id)
|
118
|
+
result = self.undoChangesWithError(error_ptr)
|
119
|
+
raise StoreError, error_ptr[0].description if error_ptr[0]
|
120
|
+
result
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
Bag = ::NSFNanoBag
|
125
|
+
end
|
126
|
+
|
127
|
+
class NSFNanoBag
|
128
|
+
include MotionPrime::BagInstanceMethods
|
129
|
+
end
|
data/motion-prime/models/base.rb
CHANGED
@@ -1,12 +1,25 @@
|
|
1
1
|
motion_require '../helpers/has_authorization'
|
2
|
+
motion_require './bag.rb'
|
3
|
+
motion_require './finder.rb'
|
4
|
+
motion_require './model.rb'
|
5
|
+
motion_require './store.rb'
|
6
|
+
motion_require './store_extension.rb'
|
2
7
|
module MotionPrime
|
3
|
-
class BaseModel <
|
8
|
+
class BaseModel < NSFNanoObject
|
4
9
|
class_attribute :sync_url
|
5
10
|
class_attribute :sync_attributes
|
6
11
|
class_attribute :_associations
|
7
12
|
alias_method :attributes, :info
|
8
13
|
include MotionPrime::HasAuthorization
|
9
14
|
|
15
|
+
include MotionPrime::ModelMethods
|
16
|
+
extend MotionPrime::ModelClassMethods
|
17
|
+
|
18
|
+
extend MotionPrime::ModelFinderMethods
|
19
|
+
include MotionPrime::ModelAssociationMethods
|
20
|
+
|
21
|
+
extend MotionPrime::ModelAssociationClassMethods
|
22
|
+
|
10
23
|
def sync_url
|
11
24
|
self.class.sync_url.to_s.gsub(':id', id.to_s)
|
12
25
|
end
|
@@ -134,15 +147,7 @@ module MotionPrime
|
|
134
147
|
end
|
135
148
|
|
136
149
|
def inspect
|
137
|
-
"#<#{self.class}:0x#{self.object_id.to_s(16)}> " +
|
138
|
-
end
|
139
|
-
|
140
|
-
# NOTE: .clear method doesn't work, using removeArray hack for now
|
141
|
-
def _clear_bag(bag_name)
|
142
|
-
bag = self.send(bag_name.to_sym)
|
143
|
-
bag_copy = bag.to_a.clone
|
144
|
-
bag - bag.to_a # this removes association from model
|
145
|
-
bag_copy.each(&:delete) # this removes collection from db
|
150
|
+
"#<#{self.class}:0x#{self.object_id.to_s(16)}> " + MotionPrime::JSON.generate(attributes)
|
146
151
|
end
|
147
152
|
|
148
153
|
def filtered_sync_attributes
|
@@ -160,61 +165,6 @@ module MotionPrime
|
|
160
165
|
def sync_attributes(*attrs)
|
161
166
|
attrs ? self.sync_attributes = attrs : super
|
162
167
|
end
|
163
|
-
|
164
|
-
def has_one(association_name, options = {})
|
165
|
-
bag_name = "#{association_name.pluralize}_bag"
|
166
|
-
self.bag bag_name.to_sym
|
167
|
-
|
168
|
-
self._associations ||= {}
|
169
|
-
self._associations[association_name] = options.merge(type: :one)
|
170
|
-
|
171
|
-
define_method("#{association_name}=") do |value|
|
172
|
-
self._clear_bag(bag_name)
|
173
|
-
|
174
|
-
self.send(:"#{bag_name}") << value
|
175
|
-
value
|
176
|
-
end
|
177
|
-
define_method("#{association_name}_attributes=") do |value|
|
178
|
-
self._clear_bag(bag_name)
|
179
|
-
|
180
|
-
association = association_name.classify.constantize.new
|
181
|
-
association.sync_with_attributes(value)
|
182
|
-
association.save
|
183
|
-
self.send(:"#{bag_name}") << association
|
184
|
-
association
|
185
|
-
end
|
186
|
-
define_method("#{association_name}") do
|
187
|
-
self.send(:"#{bag_name}").to_a.first
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
def has_many(association_name, options = {})
|
192
|
-
bag_name = "#{association_name}_bag"
|
193
|
-
self.bag bag_name.to_sym
|
194
|
-
|
195
|
-
self._associations ||= {}
|
196
|
-
self._associations[association_name] = options.merge(type: :many)
|
197
|
-
|
198
|
-
define_method("#{association_name}_attributes=") do |value|
|
199
|
-
self._clear_bag(bag_name)
|
200
|
-
|
201
|
-
association = []
|
202
|
-
value.each do |attrs|
|
203
|
-
model = association_name.classify.constantize.new
|
204
|
-
model.sync_with_attributes(attrs)
|
205
|
-
association << model
|
206
|
-
end
|
207
|
-
self.send(:"#{bag_name}=", association)
|
208
|
-
association
|
209
|
-
end
|
210
|
-
define_method("#{association_name}=") do |value|
|
211
|
-
self._clear_bag(bag_name)
|
212
|
-
self.send(:"#{bag_name}=", value)
|
213
|
-
end
|
214
|
-
define_method("#{association_name}") do
|
215
|
-
self.send(:"#{bag_name}").to_a
|
216
|
-
end
|
217
|
-
end
|
218
168
|
end
|
219
169
|
end
|
220
170
|
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
module MotionPrime
|
2
|
+
module ModelFinderMethods
|
3
|
+
# Find all models
|
4
|
+
#
|
5
|
+
# @return [Array] array of models
|
6
|
+
def all(*args)
|
7
|
+
if args[0].is_a?(Hash)
|
8
|
+
sort_options = args[0][:sort] || {}
|
9
|
+
else
|
10
|
+
sort_options = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
if sort_options.empty?
|
14
|
+
self.store.objectsOfClassNamed(self.bare_class_name)
|
15
|
+
else
|
16
|
+
sort_descriptors = sort_descriptor_with_options(sort_options)
|
17
|
+
self.store.objectsOfClassNamed(self.bare_class_name, usingSortDescriptors:sort_descriptors)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Find model by criteria
|
22
|
+
#
|
23
|
+
# Examples:
|
24
|
+
# User.find(:name, NSFEqualTo, "Bob") # => [<User#1>]
|
25
|
+
# User.find(:name => "Bob") # => [<User#1>]
|
26
|
+
# User.find(:name => {NSFEqualTo => "Bob"}) # => [<User#1>]
|
27
|
+
#
|
28
|
+
# @return [Array] array of models
|
29
|
+
def find(*arg)
|
30
|
+
if arg[0].is_a?(Hash)
|
31
|
+
# hash style
|
32
|
+
options = arg[0]
|
33
|
+
if arg[1] && arg[1].is_a?(Hash)
|
34
|
+
sort_options = arg[1][:sort] || {}
|
35
|
+
else
|
36
|
+
sort_options = {}
|
37
|
+
end
|
38
|
+
elsif arg[0] && arg[1] && arg[2]
|
39
|
+
# standard way to find
|
40
|
+
options = {arg[0] => {arg[1] => arg[2]}}
|
41
|
+
if arg[4] && arg[4].is_a?(Hash)
|
42
|
+
sort_options = arg[4][:sort] || {}
|
43
|
+
else
|
44
|
+
sort_options = {}
|
45
|
+
end
|
46
|
+
elsif arg.empty?
|
47
|
+
options = {}
|
48
|
+
sort_options = {}
|
49
|
+
else
|
50
|
+
raise "unexpected parameters #{arg}"
|
51
|
+
end
|
52
|
+
search = NSFNanoSearch.searchWithStore(self.store)
|
53
|
+
|
54
|
+
expressions = expressions_with_options(options)
|
55
|
+
search.expressions = expressions
|
56
|
+
|
57
|
+
sort_descriptors = sort_descriptor_with_options(sort_options)
|
58
|
+
search.sort = sort_descriptors
|
59
|
+
search.filterClass = self.bare_class_name
|
60
|
+
|
61
|
+
error_ptr = Pointer.new(:id)
|
62
|
+
searchResults = search.searchObjectsWithReturnType(NSFReturnObjects, error:error_ptr)
|
63
|
+
raise StoreError, error_ptr[0].description if error_ptr[0]
|
64
|
+
|
65
|
+
if searchResults.is_a?(NSDictionary)
|
66
|
+
searchResults.values
|
67
|
+
else
|
68
|
+
searchResults
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Find model keys by criteria
|
73
|
+
#
|
74
|
+
# Examples:
|
75
|
+
# User.find_keys(:name, NSFEqualTo, "Bob") # => ["1"]
|
76
|
+
# User.find_keys(:name => "Bob") # => ["1"]
|
77
|
+
# User.find_keys(:name => {NSFEqualTo => "Bob"}) # => ["1"]
|
78
|
+
#
|
79
|
+
# @return [Array] array of models
|
80
|
+
def find_keys(*arg)
|
81
|
+
if arg[0].is_a?(Hash)
|
82
|
+
# hash style
|
83
|
+
options = arg[0]
|
84
|
+
if arg[1] && arg[1].is_a?(Hash)
|
85
|
+
sort_options = arg[1][:sort] || {}
|
86
|
+
else
|
87
|
+
sort_options = {}
|
88
|
+
end
|
89
|
+
elsif arg[0] && arg[1] && arg[2]
|
90
|
+
# standard way to find
|
91
|
+
options = {arg[0] => {arg[1] => arg[2]}}
|
92
|
+
if arg[4] && arg[4].is_a?(Hash)
|
93
|
+
sort_options = arg[4][:sort] || {}
|
94
|
+
else
|
95
|
+
sort_options = {}
|
96
|
+
end
|
97
|
+
elsif arg.empty?
|
98
|
+
options = {}
|
99
|
+
sort_options = {}
|
100
|
+
else
|
101
|
+
raise "unexpected parameters #{arg}"
|
102
|
+
end
|
103
|
+
|
104
|
+
search = NSFNanoSearch.searchWithStore(self.store)
|
105
|
+
|
106
|
+
expressions = expressions_with_options(options)
|
107
|
+
search.expressions = expressions
|
108
|
+
|
109
|
+
sort_descriptors = sort_descriptor_with_options(sort_options)
|
110
|
+
search.sort = sort_descriptors
|
111
|
+
search.filterClass = self.bare_class_name
|
112
|
+
|
113
|
+
error_ptr = Pointer.new(:id)
|
114
|
+
searchResults = search.searchObjectsWithReturnType(NSFReturnKeys, error:error_ptr)
|
115
|
+
raise StoreError, error_ptr[0].description if error_ptr[0]
|
116
|
+
|
117
|
+
if searchResults.is_a?(NSDictionary)
|
118
|
+
searchResults.values
|
119
|
+
else
|
120
|
+
searchResults
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Find a model by key
|
125
|
+
#
|
126
|
+
# Examples:
|
127
|
+
# User.find_by_key(my_key)
|
128
|
+
#
|
129
|
+
# @return [Object, Nil] an object or nil (if not found)
|
130
|
+
def find_by_key(key)
|
131
|
+
search = NSFNanoSearch.searchWithStore(self.store)
|
132
|
+
search.key = key
|
133
|
+
|
134
|
+
error_ptr = Pointer.new(:id)
|
135
|
+
searchResult = search.searchObjectsWithReturnType(NSFReturnObjects, error:error_ptr).first
|
136
|
+
raise StoreError, error_ptr[0].description if error_ptr[0]
|
137
|
+
|
138
|
+
searchResult.last if searchResult
|
139
|
+
end
|
140
|
+
|
141
|
+
def bare_class_name
|
142
|
+
self.to_s.split("::").last
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
def expressions_with_options(options)
|
147
|
+
expressions = []
|
148
|
+
|
149
|
+
options.each do |key, val|
|
150
|
+
attribute = NSFNanoPredicate.predicateWithColumn(NSFAttributeColumn, matching:NSFEqualTo, value:key.to_s)
|
151
|
+
expression = NSFNanoExpression.expressionWithPredicate(attribute)
|
152
|
+
if val.is_a?(Hash)
|
153
|
+
val.each do |operator, sub_val|
|
154
|
+
value = NSFNanoPredicate.predicateWithColumn(NSFValueColumn, matching:operator, value:sub_val.to_s)
|
155
|
+
expression.addPredicate(value, withOperator:NSFAnd)
|
156
|
+
end
|
157
|
+
elsif val.is_a?(Array)
|
158
|
+
value = NSFNanoPredicate.predicateWithColumn(NSFValueColumn, matching:NSFEqualTo, value:val.pop)
|
159
|
+
expression.addPredicate(value, withOperator:NSFAnd)
|
160
|
+
|
161
|
+
val.each do |sub_val|
|
162
|
+
value = NSFNanoPredicate.predicateWithColumn(NSFValueColumn, matching:NSFEqualTo, value:sub_val.to_s)
|
163
|
+
expression.addPredicate(value, withOperator:NSFOr)
|
164
|
+
end
|
165
|
+
else
|
166
|
+
value = NSFNanoPredicate.predicateWithColumn(NSFValueColumn, matching:NSFEqualTo, value:val.to_s)
|
167
|
+
expression.addPredicate(value, withOperator:NSFAnd)
|
168
|
+
end
|
169
|
+
expressions << expression
|
170
|
+
end
|
171
|
+
return expressions
|
172
|
+
end
|
173
|
+
|
174
|
+
SORT_MAPPING = {
|
175
|
+
'asc' => true,
|
176
|
+
'desc' => false,
|
177
|
+
}
|
178
|
+
|
179
|
+
def sort_descriptor_with_options(options)
|
180
|
+
sorter = options.collect do |opt_key, opt_val|
|
181
|
+
if SORT_MAPPING.keys.include?(opt_val.to_s.downcase)
|
182
|
+
NSFNanoSortDescriptor.alloc.initWithAttribute(opt_key.to_s, ascending:SORT_MAPPING[opt_val.to_s.downcase])
|
183
|
+
else
|
184
|
+
raise "unsupported sort parameters: #{opt_val}"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module MotionPrime
|
2
|
+
module JSON
|
3
|
+
PARAMETRIZE_CLASSES = [Time, Date]
|
4
|
+
class ParserError < StandardError; end
|
5
|
+
|
6
|
+
# Parses a string or data object and converts it in data structure.
|
7
|
+
#
|
8
|
+
# @param [String, NSData] str_data the string or data to convert.
|
9
|
+
# @raise [ParserError] If the parsing of the passed string/data isn't valid.
|
10
|
+
# @return [Hash, Array, NilClass] the converted data structure, nil if the incoming string isn't valid.
|
11
|
+
def self.parse(str_data, &block)
|
12
|
+
return nil unless str_data
|
13
|
+
data = str_data.respond_to?(:to_data) ? str_data.to_data : str_data
|
14
|
+
opts = NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves | NSJSONReadingAllowFragments
|
15
|
+
error = Pointer.new(:id)
|
16
|
+
obj = NSJSONSerialization.JSONObjectWithData(data, options: opts, error: error)
|
17
|
+
raise ParserError, error[0].description if error[0]
|
18
|
+
if block_given?
|
19
|
+
yield obj
|
20
|
+
else
|
21
|
+
obj
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Generates a string from data structure.
|
26
|
+
#
|
27
|
+
# @param [String, Fixnum, Array, Hash, Nil] obj the object to serialize.
|
28
|
+
# @param [Boolean] parametrize option to parametrize data before serialization.
|
29
|
+
# @return [String] the serialized data json.
|
30
|
+
def self.generate(obj, parametrize = true)
|
31
|
+
if parametrize && obj.is_a?(Hash)
|
32
|
+
obj.each do |key, value|
|
33
|
+
obj[key] = value.to_s if PARAMETRIZE_CLASSES.include?(value.class)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
if parametrize && obj.is_a?(Array)
|
37
|
+
obj.map! do |value|
|
38
|
+
PARAMETRIZE_CLASSES.include?(value.class) ? value.to_s : value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
data = NSJSONSerialization.dataWithJSONObject(obj, options: 0, error: nil)
|
42
|
+
data.to_str
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|