motion-prime 0.8.1 → 0.8.2

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.
Files changed (41) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile.lock +1 -1
  4. data/ROADMAP.md +3 -0
  5. data/files/Gemfile +1 -1
  6. data/motion-prime/api_client.rb +5 -2
  7. data/motion-prime/core_ext/kernel.rb +36 -0
  8. data/motion-prime/elements/_content_text_mixin.rb +32 -11
  9. data/motion-prime/elements/base_element.rb +33 -16
  10. data/motion-prime/elements/draw.rb +10 -4
  11. data/motion-prime/elements/draw/image.rb +26 -15
  12. data/motion-prime/elements/map.rb +5 -0
  13. data/motion-prime/models/_association_mixin.rb +0 -3
  14. data/motion-prime/models/_base_mixin.rb +24 -3
  15. data/motion-prime/models/_filter_mixin.rb +28 -0
  16. data/motion-prime/models/_sync_mixin.rb +13 -10
  17. data/motion-prime/models/association_collection.rb +41 -16
  18. data/motion-prime/models/errors.rb +46 -24
  19. data/motion-prime/models/model.rb +8 -0
  20. data/motion-prime/screens/_sections_mixin.rb +1 -1
  21. data/motion-prime/screens/extensions/_indicators_mixin.rb +4 -2
  22. data/motion-prime/screens/extensions/_navigation_bar_mixin.rb +1 -1
  23. data/motion-prime/screens/screen.rb +4 -0
  24. data/motion-prime/sections/_async_form_mixin.rb +12 -0
  25. data/motion-prime/sections/_async_table_mixin.rb +193 -0
  26. data/motion-prime/sections/_cell_section_mixin.rb +6 -6
  27. data/motion-prime/sections/_draw_section_mixin.rb +13 -5
  28. data/motion-prime/sections/base_section.rb +62 -36
  29. data/motion-prime/sections/form.rb +19 -35
  30. data/motion-prime/sections/form/base_field_section.rb +42 -34
  31. data/motion-prime/sections/form/static_field_section.rb +9 -0
  32. data/motion-prime/sections/table.rb +143 -201
  33. data/motion-prime/sections/table/table_delegate.rb +15 -15
  34. data/motion-prime/services/table_data_indexes.rb +12 -2
  35. data/motion-prime/styles/base.rb +1 -1
  36. data/motion-prime/styles/form.rb +6 -6
  37. data/motion-prime/version.rb +1 -1
  38. data/motion-prime/views/layout.rb +5 -2
  39. data/motion-prime/views/view_styler.rb +2 -0
  40. data/spec/models/association_collection_spec.rb +28 -6
  41. metadata +6 -2
@@ -78,9 +78,7 @@ module MotionPrime
78
78
 
79
79
  association = association_name.classify.constantize.new
80
80
  association.fetch_with_attributes(value)
81
- association.save
82
81
  self.send(:"#{bag_name}") << association
83
- self.send(:"#{bag_name}").save
84
82
  association
85
83
  end
86
84
  define_method("#{association_name}") do
@@ -111,7 +109,6 @@ module MotionPrime
111
109
  end
112
110
  association_data = collection.values
113
111
  self.send(:"#{bag_name}=", association_data)
114
- self.send(:"#{bag_name}").save
115
112
  association_data
116
113
  end
117
114
  define_method("#{association_name}=") do |value|
@@ -33,7 +33,7 @@ module MotionPrime
33
33
  super || self.class.store
34
34
  end
35
35
 
36
- # Assigns attributes to model
36
+ # Assigns attributes to model
37
37
  #
38
38
  # @params attributes [Hash] attributes to be assigned
39
39
  # @params options [Hash] options
@@ -53,7 +53,7 @@ module MotionPrime
53
53
  end
54
54
  end
55
55
 
56
- # Assigns attribute to model
56
+ # Assigns attribute to model
57
57
  #
58
58
  # @params name [String, Symbol] attribute name
59
59
  # @params value [Object] attribute value
@@ -105,6 +105,13 @@ module MotionPrime
105
105
  "#<#{self.class}:0x#{self.object_id.to_s(16)}> " + MotionPrime::JSON.generate(info)
106
106
  end
107
107
 
108
+ # Returns a clone of the record with empty bags
109
+ #
110
+ # @return new [Prime::Model] model
111
+ def clone
112
+ self.class.new(self.info.select { |key, value| !key.to_s.ends_with?('_bag') })
113
+ end
114
+
108
115
  module ClassMethods
109
116
  # Initialize a new object
110
117
  #
@@ -167,7 +174,7 @@ module MotionPrime
167
174
  define_method((name + "=").to_sym) do |*args, &block|
168
175
  value = args[0]
169
176
  case options[:type].to_s
170
- when 'integer'
177
+ when 'integer'
171
178
  value = value.to_i
172
179
  when 'float'
173
180
  value = value.to_f
@@ -178,6 +185,20 @@ module MotionPrime
178
185
  self.info[name] = value
179
186
  end
180
187
 
188
+ define_method(name.to_sym) do
189
+ value = self.info[name]
190
+ case options[:type].to_s
191
+ when 'integer'
192
+ value.to_i
193
+ when 'float'
194
+ value.to_f
195
+ when 'time' && !value.is_a?(String)
196
+ value.to_short_iso8601
197
+ else
198
+ value
199
+ end
200
+ end if options[:convert]
201
+
181
202
  if options[:type].to_s == 'boolean'
182
203
  define_method("#{name}?") do
183
204
  !!self.info[name]
@@ -0,0 +1,28 @@
1
+ module MotionPrime
2
+ module FilterMixin
3
+ def filter_array(data, find_options = {}, sort_options = nil)
4
+ data = data.select do |entity|
5
+ find_options.all? { |field, value| entity.info[field] == value }
6
+ end if find_options.present?
7
+
8
+ data.sort! do |a, b|
9
+ left_part = []
10
+ right_part = []
11
+
12
+ sort_options[:sort].each do |(k,v)|
13
+ left = a.send(k)
14
+ right = b.send(k)
15
+ if left.class != right.class
16
+ left = left.to_s
17
+ right = right.to_s
18
+ end
19
+ left, right = right, left if v.to_s == 'desc'
20
+ left_part << left
21
+ right_part << right
22
+ end
23
+ left_part <=> right_part
24
+ end if sort_options.try(:[], :sort).present?
25
+ data
26
+ end
27
+ end
28
+ end
@@ -50,7 +50,7 @@ module MotionPrime
50
50
  #
51
51
  # @param options [Hash] fetch options
52
52
  # @option options [Symbol] :method Http method to calculate url, `:get` by default
53
- # @option options [Boolean] :associations Also fetch associations
53
+ # @option options [Boolean or Array] :associations Also fetch associations
54
54
  # @option options [Boolean] :save Save model after fetch
55
55
  # @param block [Proc] block to be executed after fetch
56
56
  def fetch(options = {}, &block)
@@ -59,8 +59,8 @@ module MotionPrime
59
59
  url = sync_url(method, options)
60
60
 
61
61
  will_fetch_model = !url.blank?
62
- will_fetch_associations = !options.has_key?(:associations) || options[:associations]
63
- will_fetch_associations = false unless has_associations_to_fetch?
62
+ will_fetch_associations = options.fetch(:associations, true)
63
+ will_fetch_associations = false unless has_associations_to_fetch?(options)
64
64
 
65
65
  fetch_with_url url, options do |data, status_code|
66
66
  save if options[:save]
@@ -176,13 +176,13 @@ module MotionPrime
176
176
  @associations ||= (self.class._associations || {}).clone
177
177
  end
178
178
 
179
- def associations_to_fetch
180
- @associations_to_fetch ||= associations.select { |key, v| fetch_association?(key) }
179
+ def associations_to_fetch(options = {})
180
+ associations.select { |key, v| fetch_association?(key, options) }
181
181
  end
182
182
 
183
183
  def fetch_associations(sync_options = {}, &block)
184
184
  use_callback = block_given?
185
- associations_to_fetch.keys.each_with_index do |key, index|
185
+ associations_to_fetch(sync_options).keys.each_with_index do |key, index|
186
186
  if use_callback && associations.count - 1 == index
187
187
  fetch_association(key, sync_options, &block)
188
188
  else
@@ -191,22 +191,25 @@ module MotionPrime
191
191
  end
192
192
  end
193
193
 
194
- def has_associations_to_fetch?
195
- associations_to_fetch.present?
194
+ def has_associations_to_fetch?(options = {})
195
+ associations_to_fetch(options).present?
196
196
  end
197
197
 
198
198
  def has_association?(key)
199
199
  !associations[key.to_sym].nil?
200
200
  end
201
201
 
202
- def fetch_association?(key)
202
+ def fetch_association?(key, options = {})
203
+ allowed_associations = options[:associations].map(&:to_sym) if options[:associations].is_a?(Array)
204
+ return false if allowed_associations.try(:exclude?, key.to_sym)
205
+
203
206
  options = associations[key.to_sym]
204
207
  return false if options[:if] && !options[:if].to_proc.call(self)
205
208
  association_sync_url(key, options).present?
206
209
  end
207
210
 
208
211
  def fetch_association(key, sync_options = {}, &block)
209
- return unless fetch_association?(key)
212
+ return unless fetch_association?(key, sync_options)
210
213
  options = associations[key.to_sym]
211
214
  if options[:type] == :many
212
215
  fetch_has_many(key, options, sync_options, &block)
@@ -1,10 +1,10 @@
1
1
  module MotionPrime
2
2
  class AssociationCollection < ::Array
3
+ include FilterMixin
4
+
3
5
  attr_reader :bag, :association_name
4
6
  attr_reader :inverse_relation_name, :inverse_relation_key, :model_inverse_relation_name
5
7
 
6
- delegate :<<, to: :bag
7
-
8
8
  def initialize(bag, options, *args)
9
9
  @bag = bag
10
10
  @association_name = options[:association_name]
@@ -17,7 +17,8 @@ module MotionPrime
17
17
  options[:class_name] == inverse_relation.class_name_without_kvo
18
18
  end.try(:first)
19
19
 
20
- super all(*args)
20
+ data = bag.store.present? ? find(*args) : filter(*args)
21
+ super data
21
22
  end
22
23
 
23
24
  # Initialize a new object and add to collection.
@@ -28,9 +29,7 @@ module MotionPrime
28
29
  # @params attributes [Hash] attributes beeing assigned to model
29
30
  # @return MotionPrime::Model unsaved model
30
31
  def new(attributes = {})
31
- record = model_class.new(attributes).tap do |model|
32
- set_inverse_relation_for(model)
33
- end
32
+ record = model_class.new(attributes)
34
33
  add(record)
35
34
  end
36
35
 
@@ -42,30 +41,50 @@ module MotionPrime
42
41
  # @params record [Prime::Model] model which will be added to collection.
43
42
  # @return MotionPrime::Model model
44
43
  def add(record)
44
+ set_inverse_relation_for(record)
45
45
  self.bag << record
46
46
  record
47
47
  end
48
+ alias_method :<<, :add
48
49
 
49
50
  # Return all association records.
50
51
  #
51
52
  # @example:
52
53
  # project.users.all
53
- # project.users.all(age: 10)
54
+ #
55
+ # @return Array<MotionPrime::Model> association records
56
+ def all
57
+ data = bag.to_a
58
+ set_inverse_relation_for(data)
59
+ data
60
+ end
61
+ alias_method :to_a, :all
62
+
63
+ # Find association records.
64
+ #
65
+ # @example:
66
+ # project.users.find(age: 10)
54
67
  #
55
68
  # @params find_options [Hash] finder options.
56
69
  # @params sort_options [Hash] sorting options.
57
70
  # @return Array<MotionPrime::Model> association records
58
- def all(find_options = nil, sort_options = nil)
71
+ def find(find_options = {}, sort_options = nil)
72
+ raise "Use `filter` method when bag has not been saved yet" unless bag.store.present?
73
+
59
74
  find_options = build_find_options(find_options)
60
75
  sort_options = build_sort_options(sort_options)
61
76
 
62
- data = if bag.store.present?
63
- bag.find(find_options, sort_options)
64
- else
65
- bag.to_a.select do |entity|
66
- find_options.all? { |field, value| entity.info[field] == value }
67
- end
68
- end
77
+ data = bag.find(find_options, sort_options)
78
+ set_inverse_relation_for(data)
79
+ data
80
+ end
81
+
82
+ def filter(find_options = {}, sort_options = nil)
83
+ find_options = build_find_options(find_options)
84
+ sort_options = build_sort_options(sort_options)
85
+
86
+ data = filter_array(bag.to_a, find_options, sort_options)
87
+
69
88
  set_inverse_relation_for(data)
70
89
  data
71
90
  end
@@ -96,7 +115,13 @@ module MotionPrime
96
115
  end
97
116
 
98
117
  def build_sort_options(options)
99
- options || {sort: model_class.default_sort_options}
118
+ options || begin
119
+ {sort: model_class.default_sort_options} if has_default_sort?
120
+ end
121
+ end
122
+
123
+ def has_default_sort?
124
+ model_class.default_sort_options.present?
100
125
  end
101
126
 
102
127
  def set_inverse_relation_for(models)
@@ -1,41 +1,39 @@
1
1
  module MotionPrime
2
2
  class Errors
3
- attr_accessor :_unique_keys
3
+ attr_accessor :info
4
+ attr_reader :changes
4
5
 
5
6
  def initialize(model)
6
- @_unique_keys = []
7
+ @info = MotionSupport::HashWithIndifferentAccess.new
8
+ @changes = MotionSupport::HashWithIndifferentAccess.new
7
9
  @model = model
8
10
  model.class.attributes.map(&:to_sym).each do |key|
9
11
  initialize_for_key key
10
12
  end
11
13
  end
12
14
 
13
- def unique_key(key)
14
- [key, @model.object_id].join('_').to_sym
15
- end
16
-
17
- def initialize_for_key(key)
18
- unique_key = unique_key(key)
19
-
20
- return if @_unique_keys.include?(unique_key)
21
- @_unique_keys << unique_key
22
- instance_variable_set("@#{unique_key}", [])
23
- self.class.send :attr_accessor, unique_key
15
+ def to_hash
16
+ @info
24
17
  end
25
18
 
26
19
  def get(key)
27
20
  initialize_for_key(key)
28
- send(unique_key(key))
21
+ to_hash[key]
29
22
  end
30
23
 
31
- def set(key, errors)
24
+ def set(key, errors, options = {})
32
25
  initialize_for_key(key)
33
- send :"#{unique_key(key)}=", Array.wrap(errors)
26
+
27
+ track_changed options do
28
+ to_hash[key] = Array.wrap(errors)
29
+ end
34
30
  end
35
31
 
36
- def add(key, error)
32
+ def add(key, error, options = {})
37
33
  initialize_for_key(key)
38
- send(unique_key(key)) << error
34
+ track_changed do
35
+ to_hash[key] << error
36
+ end
39
37
  end
40
38
 
41
39
  def [](key)
@@ -46,22 +44,26 @@ module MotionPrime
46
44
  set(key, errors)
47
45
  end
48
46
 
49
- def reset_for(key)
50
- send :"#{unique_key(key)}=", []
47
+ def reset_for(key, options = {})
48
+ track_changed options do
49
+ to_hash[key] = []
50
+ end
51
51
  end
52
52
 
53
53
  def reset
54
- @_unique_keys.each do |unique_key|
55
- send :"#{unique_key}=", []
54
+ track_changed do
55
+ to_hash.keys.each do |key|
56
+ reset_for(key, silent: true)
57
+ end
56
58
  end
57
59
  end
58
60
 
59
61
  def messages
60
- @_unique_keys.map{ |uniq_k| send(uniq_k) }.compact.flatten
62
+ to_hash.values.flatten
61
63
  end
62
64
 
63
65
  def blank?
64
- messages.blank?
66
+ messages.none?
65
67
  end
66
68
 
67
69
  def present?
@@ -71,5 +73,25 @@ module MotionPrime
71
73
  def to_s
72
74
  messages.join(';')
73
75
  end
76
+
77
+ def track_changed(options = {})
78
+ return yield if options[:silent]
79
+ @changes = MotionSupport::HashWithIndifferentAccess.new
80
+ saved_info = to_hash.clone
81
+ willChangeValueForKey(:info)
82
+ yield
83
+ to_hash.each do |key, value|
84
+ @changes[key] = [value, saved_info[key]] unless value == saved_info[key]
85
+ end
86
+ didChangeValueForKey(:info)
87
+ end
88
+
89
+ private
90
+ def initialize_for_key(key)
91
+ key = key.to_sym
92
+ return if @info.has_key?(key)
93
+
94
+ to_hash[key] ||= []
95
+ end
74
96
  end
75
97
  end
@@ -24,6 +24,14 @@ module MotionPrime
24
24
  @errors ||= Errors.new(self.weak_ref)
25
25
  end
26
26
 
27
+ def set_errors(data)
28
+ errors.track_changed do
29
+ data.symbolize_keys.each do |key, error_messages|
30
+ errors.set(key, error_messages, silent: true) if error_messages.present?
31
+ end
32
+ end
33
+ end
34
+
27
35
  def dealloc
28
36
  Prime.logger.dealloc_message :model, self
29
37
  super
@@ -15,7 +15,7 @@ module MotionPrime
15
15
  end
16
16
 
17
17
  def all_sections
18
- @sections.values
18
+ Array.wrap(@sections.try(:values))
19
19
  end
20
20
 
21
21
  def create_sections
@@ -38,8 +38,10 @@ module MotionPrime
38
38
  when 'alert' then MBAlertViewHUDTypeExclamationMark
39
39
  else MBAlertViewHUDTypeCheckmark
40
40
  end
41
- MBHUDView.hudWithBody message,
42
- type: hud_type, hidesAfter: time, show: true
41
+
42
+ unless time === false
43
+ MBHUDView.hudWithBody(message, type: hud_type, hidesAfter: time, show: true)
44
+ end
43
45
  end
44
46
 
45
47
  def show_spinner(message = nil)
@@ -58,8 +58,8 @@ module MotionPrime
58
58
  face = UIButton.buttonWithType UIButtonTypeCustom
59
59
  face.setImage(image, forState: UIControlStateNormal)
60
60
  face.setTitle(title, forState: UIControlStateNormal)
61
- face.bounds = CGRectMake(0, 0, 100, 60)
62
61
  face.setContentHorizontalAlignment UIControlContentHorizontalAlignmentLeft
62
+ face.sizeToFit
63
63
  face.on :touch do
64
64
  args[:action].to_proc.call(self)
65
65
  end