motion-prime 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
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