motion-prime 0.4.5 → 0.5.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.
- checksums.yaml +14 -6
- data/.gitignore +1 -0
- data/CHANGELOG.md +14 -0
- data/Gemfile.lock +1 -1
- data/README.md +10 -8
- data/ROADMAP.md +9 -10
- data/Rakefile +1 -1
- data/doc/code/getting_started.rb +3 -3
- data/doc/code/screens.rb +25 -10
- data/doc/code/sections.rb +41 -0
- data/doc/docs/getting_started.html +8 -3
- data/doc/docs/screens.html +58 -19
- data/doc/docs/sections.html +134 -0
- data/files/Gemfile +7 -1
- data/files/Gemfile.lock +18 -9
- data/files/Rakefile +2 -11
- data/files/app/app_delegate.rb +2 -2
- data/files/app/config/base.rb +0 -4
- data/files/app/screens/application_screen.rb +2 -2
- data/files/app/screens/home_screen.rb +2 -0
- data/files/app/screens/sidebar_screen.rb +3 -3
- data/files/app/sections/home/section.rb +3 -0
- data/files/app/sections/sidebar/action.rb +1 -1
- data/files/app/styles/home.rb +9 -0
- data/files/app/styles/sidebar.rb +3 -3
- data/lib/motion-prime.rb +9 -0
- data/motion-prime/api_client.rb +5 -1
- data/motion-prime/app_delegate.rb +40 -45
- data/motion-prime/core_ext/kernel.rb +8 -0
- data/motion-prime/elements/_content_text_mixin.rb +3 -1
- data/motion-prime/elements/_text_mixin.rb +1 -1
- data/motion-prime/elements/base_element.rb +6 -5
- data/motion-prime/elements/draw/image.rb +6 -1
- data/motion-prime/elements/view_with_section.rb +7 -0
- data/motion-prime/helpers/has_style_chain_builder.rb +4 -1
- data/motion-prime/models/_association_mixin.rb +139 -0
- data/motion-prime/models/_base_mixin.rb +184 -0
- data/motion-prime/models/_dirty_mixin.rb +31 -0
- data/motion-prime/models/_finder_mixin.rb +208 -0
- data/motion-prime/models/{bag.rb → _nano_bag_mixin.rb} +4 -4
- data/motion-prime/models/{sync.rb → _sync_mixin.rb} +75 -76
- data/motion-prime/models/json.rb +6 -9
- data/motion-prime/models/model.rb +24 -172
- data/motion-prime/{mp.rb → prime.rb} +0 -1
- data/motion-prime/screens/_base_mixin.rb +9 -10
- data/motion-prime/screens/_navigation_mixin.rb +12 -17
- data/motion-prime/screens/extensions/_indicators_mixin.rb +1 -1
- data/motion-prime/screens/extensions/_navigation_bar_mixin.rb +3 -3
- data/motion-prime/screens/{base_screen.rb → screen.rb} +11 -5
- data/motion-prime/sections/_cell_section_mixin.rb +13 -0
- data/motion-prime/sections/_draw_section_mixin.rb +17 -0
- data/motion-prime/sections/base_section.rb +51 -16
- data/motion-prime/sections/form/base_field_section.rb +27 -16
- data/motion-prime/sections/form/base_header_section.rb +4 -3
- data/motion-prime/sections/form/password_field_section.rb +4 -0
- data/motion-prime/sections/form/select_field_section.rb +4 -0
- data/motion-prime/sections/form/string_field_section.rb +4 -0
- data/motion-prime/sections/form/submit_field_section.rb +4 -0
- data/motion-prime/sections/form/text_field_section.rb +4 -0
- data/motion-prime/sections/form.rb +7 -1
- data/motion-prime/sections/tabbed.rb +1 -1
- data/motion-prime/sections/table/table_delegate.rb +5 -0
- data/motion-prime/sections/table.rb +71 -51
- data/motion-prime/services/logger.rb +37 -0
- data/motion-prime/support/mp_cell_with_section.rb +9 -4
- data/motion-prime/support/tab_bar_controller.rb +2 -1
- data/motion-prime/version.rb +1 -1
- data/motion-prime/views/layout.rb +1 -1
- data/motion-prime/views/view_builder.rb +7 -0
- data/motion-prime.gemspec +1 -1
- data/spec/delegate/{base_delegate_spec.rb → delegate_spec.rb} +1 -1
- data/spec/helpers/{base_delegate.rb → delegates.rb} +0 -0
- data/spec/helpers/models.rb +11 -18
- data/spec/helpers/{base_screen.rb → screens.rb} +1 -1
- data/spec/models/{association_spec.rb → associations_spec.rb} +1 -1
- data/spec/models/bag_spec.rb +1 -1
- data/spec/models/errors_spec.rb +1 -1
- data/spec/models/finder_spec.rb +1 -1
- data/spec/models/json.rb +87 -0
- data/spec/models/model_spec.rb +23 -3
- data/spec/models/store_extension_spec.rb +1 -1
- data/spec/models/store_spec.rb +1 -1
- data/spec/screens/{base_screen_spec.rb → screen_spec.rb} +5 -1
- metadata +52 -43
- data/motion-prime/models/association.rb +0 -134
- data/motion-prime/models/base.rb +0 -27
- data/motion-prime/models/finder.rb +0 -202
- data/motion-prime/screens/sidebar_container_screen.rb +0 -80
@@ -0,0 +1,139 @@
|
|
1
|
+
module MotionPrime
|
2
|
+
module ModelAssociationMixin
|
3
|
+
extend ::MotionSupport::Concern
|
4
|
+
|
5
|
+
def _bags
|
6
|
+
@_bags ||= {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def save
|
10
|
+
_bags.values.each do |bag|
|
11
|
+
bag.store = self.store
|
12
|
+
bag.save
|
13
|
+
end
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
# Defines bag associated with model, creates accessor for bag
|
19
|
+
#
|
20
|
+
# @param [String] name - the name of bag
|
21
|
+
# @return [Nil]
|
22
|
+
def bag(name)
|
23
|
+
define_method(name) do |*args, &block|
|
24
|
+
return _bags[name] if _bags[name]
|
25
|
+
bag_key = self.info[name]
|
26
|
+
if bag_key.present?
|
27
|
+
bag = self.class.store.bagsWithKeysInArray([bag_key]).first
|
28
|
+
end
|
29
|
+
unless bag
|
30
|
+
bag = Bag.bag
|
31
|
+
self.info[name] = bag.key
|
32
|
+
end
|
33
|
+
|
34
|
+
_bags[name] = bag
|
35
|
+
end
|
36
|
+
|
37
|
+
define_method((name + "=").to_sym) do |*args, &block|
|
38
|
+
bag = self.send(name)
|
39
|
+
case args[0]
|
40
|
+
when Bag
|
41
|
+
bag.clear
|
42
|
+
bag += args[0].saved.values
|
43
|
+
when Array
|
44
|
+
bag.clear
|
45
|
+
bag += args[0]
|
46
|
+
else
|
47
|
+
raise StoreError, "Unexpected type assigned to bags, must be an Array or MotionPrime::Bag, now: #{args[0].class}"
|
48
|
+
end
|
49
|
+
bag
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Defines has one association for model, creates accessor for association
|
54
|
+
#
|
55
|
+
# @param [String] name - the name of association
|
56
|
+
# @return [Nil]
|
57
|
+
def has_one(association_name, options = {})
|
58
|
+
bag_name = "#{association_name.pluralize}_bag"
|
59
|
+
self.bag bag_name.to_sym
|
60
|
+
|
61
|
+
self._associations ||= {}
|
62
|
+
self._associations[association_name] = options.merge(type: :one)
|
63
|
+
|
64
|
+
define_method("#{association_name}=") do |value|
|
65
|
+
self.send(bag_name).clear
|
66
|
+
self.send(:"#{bag_name}") << value
|
67
|
+
value
|
68
|
+
end
|
69
|
+
define_method("#{association_name}_attributes=") do |value|
|
70
|
+
self.send(bag_name).clear
|
71
|
+
|
72
|
+
association = association_name.classify.constantize.new
|
73
|
+
association.fetch_with_attributes(value)
|
74
|
+
association.save
|
75
|
+
self.send(:"#{bag_name}") << association
|
76
|
+
self.send(:"#{bag_name}").save
|
77
|
+
association
|
78
|
+
end
|
79
|
+
define_method("#{association_name}") do
|
80
|
+
self.send(:"#{bag_name}").to_a.first
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Defines has many association for model, creates accessor for association
|
85
|
+
#
|
86
|
+
# @param [String] name - the name of association
|
87
|
+
# @return [Nil]
|
88
|
+
def has_many(association_name, options = {})
|
89
|
+
bag_name = "#{association_name}_bag"
|
90
|
+
self.bag bag_name.to_sym
|
91
|
+
|
92
|
+
self._associations ||= {}
|
93
|
+
self._associations[association_name] = options.merge(type: :many)
|
94
|
+
|
95
|
+
define_method("#{association_name}_attributes=") do |value|
|
96
|
+
self.send(bag_name).clear
|
97
|
+
|
98
|
+
pending_save_counter = 0
|
99
|
+
collection = value.inject({}) do |result, attrs|
|
100
|
+
model = association_name.classify.constantize.new
|
101
|
+
model.fetch_with_attributes(attrs)
|
102
|
+
unique_key = model.id || "pending_#{pending_save_counter+=1}"
|
103
|
+
result.merge(unique_key => model)
|
104
|
+
end
|
105
|
+
association_data = collection.values
|
106
|
+
self.send(:"#{bag_name}=", association_data)
|
107
|
+
self.send(:"#{bag_name}").save
|
108
|
+
association_data
|
109
|
+
end
|
110
|
+
define_method("#{association_name}=") do |value|
|
111
|
+
self.send(bag_name).clear
|
112
|
+
self.send(:"#{bag_name}=", value)
|
113
|
+
end
|
114
|
+
define_method("#{association_name}") do |*args|
|
115
|
+
bag = self.send(:"#{bag_name}")
|
116
|
+
collection_options = {
|
117
|
+
association_name: association_name,
|
118
|
+
inverse_relation: {
|
119
|
+
type: :has_one,
|
120
|
+
name: self.class_name_without_kvo.demodulize.underscore,
|
121
|
+
instance: self
|
122
|
+
}
|
123
|
+
}
|
124
|
+
AssociationCollection.new(bag, collection_options, *args)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def belongs_to(association_name, options = {})
|
129
|
+
self._associations ||= {}
|
130
|
+
self._associations[association_name] = {
|
131
|
+
type: :belongs_to_one,
|
132
|
+
class_name: association_name.classify
|
133
|
+
}.merge(options)
|
134
|
+
|
135
|
+
self.send(:attr_accessor, association_name)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
module MotionPrime
|
2
|
+
module ModelBaseMixin
|
3
|
+
extend ::MotionSupport::Concern
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.class_attribute :default_sort_options
|
7
|
+
end
|
8
|
+
|
9
|
+
def save
|
10
|
+
raise StoreError, 'No store provided' unless self.store
|
11
|
+
error_ptr = Pointer.new(:id)
|
12
|
+
self.store.addObject(self, error: error_ptr)
|
13
|
+
raise StoreError, error_ptr[0].description if error_ptr[0]
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete
|
18
|
+
raise StoreError, 'No store provided' unless self.store
|
19
|
+
|
20
|
+
error_ptr = Pointer.new(:id)
|
21
|
+
self.store.removeObject(self, error: error_ptr)
|
22
|
+
raise StoreError, error_ptr[0].description if error_ptr[0]
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def store
|
27
|
+
super || self.class.store
|
28
|
+
end
|
29
|
+
|
30
|
+
def assign_attributes(new_attributes, options = {})
|
31
|
+
attributes = new_attributes.symbolize_keys
|
32
|
+
attributes.each do |k, v|
|
33
|
+
if has_attribute?(k)
|
34
|
+
assign_attribute(k, v) unless options[:skip_nil_values] && v.nil?
|
35
|
+
elsif options[:validate_attribute_presence]
|
36
|
+
raise(StoreError, "unknown attribute: '#{k}'")
|
37
|
+
else
|
38
|
+
NSLog("unknown attribute: #{k}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def assign_attribute(name, value)
|
44
|
+
self.send("#{name}=", value) if has_attribute?(name)
|
45
|
+
end
|
46
|
+
|
47
|
+
def has_attribute?(name)
|
48
|
+
respond_to?("#{name}=")
|
49
|
+
end
|
50
|
+
|
51
|
+
def attributes_hash
|
52
|
+
self.info.to_hash.symbolize_keys
|
53
|
+
end
|
54
|
+
|
55
|
+
def new_record?
|
56
|
+
id.blank?
|
57
|
+
end
|
58
|
+
|
59
|
+
def persisted?
|
60
|
+
!new_record?
|
61
|
+
end
|
62
|
+
|
63
|
+
def model_name
|
64
|
+
self.class_name_without_kvo.underscore
|
65
|
+
end
|
66
|
+
|
67
|
+
def inspect
|
68
|
+
"#<#{self.class}:0x#{self.object_id.to_s(16)}> " + MotionPrime::JSON.generate(info)
|
69
|
+
end
|
70
|
+
|
71
|
+
module ClassMethods
|
72
|
+
# Initialize a new object
|
73
|
+
#
|
74
|
+
# Examples:
|
75
|
+
# User.new(name: "Bob", age: 10)
|
76
|
+
#
|
77
|
+
# @return MotionPrime::Model unsaved model
|
78
|
+
def new(data = {}, options = {})
|
79
|
+
data.keys.each do |key|
|
80
|
+
unless self.attributes.member? key.to_sym
|
81
|
+
if options[:validate_attribute_presence]
|
82
|
+
raise StoreError, "unknown attribute: '#{key}'"
|
83
|
+
else
|
84
|
+
data.delete(key)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
object = self.nanoObjectWithDictionary(data)
|
90
|
+
object
|
91
|
+
end
|
92
|
+
|
93
|
+
# Initialize a new object and save it
|
94
|
+
#
|
95
|
+
# Examples:
|
96
|
+
# User.create(name: "Bob", age: 10)
|
97
|
+
#
|
98
|
+
# @return MotionPrime::Model saved model
|
99
|
+
def create(data = {})
|
100
|
+
object = self.new(data)
|
101
|
+
object.save
|
102
|
+
end
|
103
|
+
|
104
|
+
# Define model attribute
|
105
|
+
#
|
106
|
+
# Examples:
|
107
|
+
# class User < MotionPrime::Model
|
108
|
+
# attribute :name
|
109
|
+
# attribute :age
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
# @return Nil
|
113
|
+
def attribute(name, options = {})
|
114
|
+
attributes << name
|
115
|
+
|
116
|
+
define_method(name) do |*args, &block|
|
117
|
+
self.info[name]
|
118
|
+
end
|
119
|
+
|
120
|
+
define_method((name + "=").to_sym) do |*args, &block|
|
121
|
+
value = args[0]
|
122
|
+
case options[:type].to_s
|
123
|
+
when 'integer' then value = value.to_i
|
124
|
+
when 'float' then value = value.to_f
|
125
|
+
end unless value.nil?
|
126
|
+
|
127
|
+
self.info[name] = value
|
128
|
+
end
|
129
|
+
|
130
|
+
if options[:type].to_s == 'boolean'
|
131
|
+
define_method("#{name}?") do
|
132
|
+
!!self.info[name]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Set or return all model attribute names
|
138
|
+
#
|
139
|
+
# @return Array array of attribute names
|
140
|
+
def attributes(*attrs)
|
141
|
+
if attrs.size > 0
|
142
|
+
attrs.each{|attr| attribute attr}
|
143
|
+
else
|
144
|
+
@attributes ||= []
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Return store associated with model class, or shared store by default
|
149
|
+
#
|
150
|
+
# @return MotionPrime::Store store
|
151
|
+
def store
|
152
|
+
@store ||= MotionPrime::Store.shared_store
|
153
|
+
end
|
154
|
+
|
155
|
+
# Define store associated with model class
|
156
|
+
#
|
157
|
+
# @param MotionPrime::Store store
|
158
|
+
# @return MotionPrime::Store store
|
159
|
+
def store=(store)
|
160
|
+
@store = store
|
161
|
+
end
|
162
|
+
|
163
|
+
# Count of models
|
164
|
+
#
|
165
|
+
# @return Fixnum count
|
166
|
+
def count
|
167
|
+
self.store.count(self)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Delete objects from store
|
171
|
+
#
|
172
|
+
# @param [Array, MotionPrime::Model] objects to delete
|
173
|
+
# @return [Array] result
|
174
|
+
def delete(*args)
|
175
|
+
keys = find_keys(*args)
|
176
|
+
self.store.delete_keys(keys)
|
177
|
+
end
|
178
|
+
|
179
|
+
def default_sort(sort_options)
|
180
|
+
self.default_sort_options = sort_options
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module MotionPrime
|
2
|
+
module ModelDirtyMixin
|
3
|
+
extend ::MotionSupport::Concern
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.class_attribute :_changed_attributes
|
7
|
+
end
|
8
|
+
|
9
|
+
def track_changed_attributes(&block)
|
10
|
+
@_changed_attributes ||= {}
|
11
|
+
old_attrs = self.info.clone
|
12
|
+
result = block.call
|
13
|
+
new_attrs = self.info.clone
|
14
|
+
new_attrs.each do |key, value|
|
15
|
+
if value != old_attrs[key] && ! @_changed_attributes.has_key?(key.to_s)
|
16
|
+
@_changed_attributes[key.to_s] = old_attrs[key]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
result
|
20
|
+
end
|
21
|
+
|
22
|
+
def has_changed?(key = nil)
|
23
|
+
@_changed_attributes ||= {}
|
24
|
+
if key
|
25
|
+
@_changed_attributes.has_key?(key.to_s)
|
26
|
+
else
|
27
|
+
@_changed_attributes.present?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
module MotionPrime
|
2
|
+
module ModelFinderMixin
|
3
|
+
extend ::MotionSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
attr_accessor :bare_class
|
7
|
+
|
8
|
+
# Find all models
|
9
|
+
#
|
10
|
+
# @return [Array] array of models
|
11
|
+
def all(*args)
|
12
|
+
return [] unless self.store
|
13
|
+
|
14
|
+
if args[0].is_a?(Hash)
|
15
|
+
sort_options = args[0][:sort] || {}
|
16
|
+
else
|
17
|
+
sort_options = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
if sort_options.empty?
|
21
|
+
self.store.objectsOfClassNamed(self.bare_class_name)
|
22
|
+
else
|
23
|
+
sort_descriptors = sort_descriptor_with_options(sort_options)
|
24
|
+
self.store.objectsOfClassNamed(self.bare_class_name, usingSortDescriptors:sort_descriptors)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Find last model model by default order
|
29
|
+
def last
|
30
|
+
all.last
|
31
|
+
end
|
32
|
+
|
33
|
+
# Find first model model by default order
|
34
|
+
def first
|
35
|
+
all.first
|
36
|
+
end
|
37
|
+
|
38
|
+
# Find model by criteria
|
39
|
+
#
|
40
|
+
# Examples:
|
41
|
+
# User.find(:name, NSFEqualTo, "Bob") # => [<User#1>]
|
42
|
+
# User.find(:name => "Bob") # => [<User#1>]
|
43
|
+
# User.find(:name => {NSFEqualTo => "Bob"}) # => [<User#1>]
|
44
|
+
#
|
45
|
+
# @return [Array] array of models
|
46
|
+
def find(*arg)
|
47
|
+
if arg[0].is_a?(Hash)
|
48
|
+
# hash style
|
49
|
+
options = arg[0]
|
50
|
+
if arg[1] && arg[1].is_a?(Hash)
|
51
|
+
sort_options = arg[1][:sort] || {}
|
52
|
+
else
|
53
|
+
sort_options = {}
|
54
|
+
end
|
55
|
+
elsif arg[0] && arg[1] && arg[2]
|
56
|
+
# standard way to find
|
57
|
+
options = {arg[0] => {arg[1] => arg[2]}}
|
58
|
+
if arg[4] && arg[4].is_a?(Hash)
|
59
|
+
sort_options = arg[4][:sort] || {}
|
60
|
+
else
|
61
|
+
sort_options = {}
|
62
|
+
end
|
63
|
+
elsif arg.empty?
|
64
|
+
options = {}
|
65
|
+
sort_options = {}
|
66
|
+
else
|
67
|
+
raise "unexpected parameters #{arg}"
|
68
|
+
end
|
69
|
+
search = NSFNanoSearch.searchWithStore(self.store)
|
70
|
+
|
71
|
+
expressions = expressions_with_options(options)
|
72
|
+
search.expressions = expressions
|
73
|
+
|
74
|
+
sort_descriptors = sort_descriptor_with_options(sort_options)
|
75
|
+
search.sort = sort_descriptors
|
76
|
+
search.filterClass = self.bare_class_name
|
77
|
+
|
78
|
+
error_ptr = Pointer.new(:id)
|
79
|
+
searchResults = search.searchObjectsWithReturnType(NSFReturnObjects, error:error_ptr)
|
80
|
+
raise StoreError, error_ptr[0].description if error_ptr[0]
|
81
|
+
|
82
|
+
if searchResults.is_a?(NSDictionary)
|
83
|
+
searchResults.values
|
84
|
+
else
|
85
|
+
searchResults
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Find model keys by criteria
|
90
|
+
#
|
91
|
+
# Examples:
|
92
|
+
# User.find_keys(:name, NSFEqualTo, "Bob") # => ["1"]
|
93
|
+
# User.find_keys(:name => "Bob") # => ["1"]
|
94
|
+
# User.find_keys(:name => {NSFEqualTo => "Bob"}) # => ["1"]
|
95
|
+
#
|
96
|
+
# @return [Array] array of models
|
97
|
+
def find_keys(*arg)
|
98
|
+
if arg[0].is_a?(Hash)
|
99
|
+
# hash style
|
100
|
+
options = arg[0]
|
101
|
+
if arg[1] && arg[1].is_a?(Hash)
|
102
|
+
sort_options = arg[1][:sort] || {}
|
103
|
+
else
|
104
|
+
sort_options = {}
|
105
|
+
end
|
106
|
+
elsif arg[0] && arg[1] && arg[2]
|
107
|
+
# standard way to find
|
108
|
+
options = {arg[0] => {arg[1] => arg[2]}}
|
109
|
+
if arg[4] && arg[4].is_a?(Hash)
|
110
|
+
sort_options = arg[4][:sort] || {}
|
111
|
+
else
|
112
|
+
sort_options = {}
|
113
|
+
end
|
114
|
+
elsif arg.empty?
|
115
|
+
options = {}
|
116
|
+
sort_options = {}
|
117
|
+
else
|
118
|
+
raise "unexpected parameters #{arg}"
|
119
|
+
end
|
120
|
+
|
121
|
+
search = NSFNanoSearch.searchWithStore(self.store)
|
122
|
+
|
123
|
+
expressions = expressions_with_options(options)
|
124
|
+
search.expressions = expressions
|
125
|
+
|
126
|
+
sort_descriptors = sort_descriptor_with_options(sort_options)
|
127
|
+
search.sort = sort_descriptors
|
128
|
+
search.filterClass = self.bare_class_name
|
129
|
+
|
130
|
+
error_ptr = Pointer.new(:id)
|
131
|
+
searchResults = search.searchObjectsWithReturnType(NSFReturnKeys, error:error_ptr)
|
132
|
+
raise StoreError, error_ptr[0].description if error_ptr[0]
|
133
|
+
|
134
|
+
if searchResults.is_a?(NSDictionary)
|
135
|
+
searchResults.values
|
136
|
+
else
|
137
|
+
searchResults
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Find a model by key
|
142
|
+
#
|
143
|
+
# Examples:
|
144
|
+
# User.find_by_key(my_key)
|
145
|
+
#
|
146
|
+
# @return [Object, Nil] an object or nil (if not found)
|
147
|
+
def find_by_key(key)
|
148
|
+
search = NSFNanoSearch.searchWithStore(self.store)
|
149
|
+
search.key = key
|
150
|
+
|
151
|
+
error_ptr = Pointer.new(:id)
|
152
|
+
searchResult = search.searchObjectsWithReturnType(NSFReturnObjects, error:error_ptr).first
|
153
|
+
raise StoreError, error_ptr[0].description if error_ptr[0]
|
154
|
+
|
155
|
+
searchResult.last if searchResult
|
156
|
+
end
|
157
|
+
|
158
|
+
def bare_class_name
|
159
|
+
subject = @bare_class || self
|
160
|
+
subject.to_s.split("::").last
|
161
|
+
end
|
162
|
+
|
163
|
+
private
|
164
|
+
def expressions_with_options(options)
|
165
|
+
expressions = []
|
166
|
+
|
167
|
+
options.each do |key, val|
|
168
|
+
attribute = NSFNanoPredicate.predicateWithColumn(NSFAttributeColumn, matching:NSFEqualTo, value:key.to_s)
|
169
|
+
expression = NSFNanoExpression.expressionWithPredicate(attribute)
|
170
|
+
if val.is_a?(Hash)
|
171
|
+
val.each do |operator, sub_val|
|
172
|
+
value = NSFNanoPredicate.predicateWithColumn(NSFValueColumn, matching:operator, value:sub_val.to_s)
|
173
|
+
expression.addPredicate(value, withOperator:NSFAnd)
|
174
|
+
end
|
175
|
+
elsif val.is_a?(Array)
|
176
|
+
value = NSFNanoPredicate.predicateWithColumn(NSFValueColumn, matching:NSFEqualTo, value:val.pop)
|
177
|
+
expression.addPredicate(value, withOperator:NSFAnd)
|
178
|
+
|
179
|
+
val.each do |sub_val|
|
180
|
+
value = NSFNanoPredicate.predicateWithColumn(NSFValueColumn, matching:NSFEqualTo, value:sub_val.to_s)
|
181
|
+
expression.addPredicate(value, withOperator:NSFOr)
|
182
|
+
end
|
183
|
+
else
|
184
|
+
value = NSFNanoPredicate.predicateWithColumn(NSFValueColumn, matching:NSFEqualTo, value:val.to_s)
|
185
|
+
expression.addPredicate(value, withOperator:NSFAnd)
|
186
|
+
end
|
187
|
+
expressions << expression
|
188
|
+
end
|
189
|
+
return expressions
|
190
|
+
end
|
191
|
+
|
192
|
+
SORT_MAPPING = {
|
193
|
+
'asc' => true,
|
194
|
+
'desc' => false,
|
195
|
+
}
|
196
|
+
|
197
|
+
def sort_descriptor_with_options(options)
|
198
|
+
sorter = options.collect do |opt_key, opt_val|
|
199
|
+
if SORT_MAPPING.keys.include?(opt_val.to_s.downcase)
|
200
|
+
NSFNanoSortDescriptor.alloc.initWithAttribute(opt_key.to_s, ascending:SORT_MAPPING[opt_val.to_s.downcase])
|
201
|
+
else
|
202
|
+
raise "unsupported sort parameters: #{opt_val}"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
|
-
motion_require './
|
1
|
+
motion_require './_finder_mixin.rb'
|
2
2
|
module MotionPrime
|
3
|
-
module
|
3
|
+
module NanoBagMixin
|
4
4
|
def self.included(base)
|
5
5
|
base.class_eval do
|
6
6
|
alias_method :saved, :savedObjects
|
@@ -131,6 +131,6 @@ module MotionPrime
|
|
131
131
|
end
|
132
132
|
|
133
133
|
class NSFNanoBag
|
134
|
-
include MotionPrime::
|
135
|
-
include MotionPrime::
|
134
|
+
include MotionPrime::ModelFinderMixin::ClassMethods
|
135
|
+
include MotionPrime::NanoBagMixin
|
136
136
|
end
|