motion-prime 0.2.1 → 0.3.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.
Files changed (81) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGELOG.md +5 -0
  3. data/Gemfile.lock +14 -11
  4. data/README.md +8 -11
  5. data/Rakefile +2 -1
  6. data/bin/prime.rb +47 -0
  7. data/doc/FAQ.md +1 -1
  8. data/files/app/app_delegate.rb +1 -1
  9. data/files/app/config/base.rb +8 -4
  10. data/files/app/screens/application_screen.rb +1 -1
  11. data/files/app/screens/sidebar_screen.rb +1 -1
  12. data/files/app/sections/sidebar/action.rb +1 -1
  13. data/files/app/sections/sidebar/table.rb +1 -1
  14. data/files/app/styles/sidebar.rb +5 -5
  15. data/motion-prime.gemspec +1 -0
  16. data/motion-prime/api_client.rb +81 -0
  17. data/motion-prime/app_delegate.rb +22 -5
  18. data/motion-prime/config/base.rb +5 -0
  19. data/motion-prime/core_ext/kernel.rb +5 -0
  20. data/motion-prime/elements/_field_dimensions_mixin.rb +43 -0
  21. data/motion-prime/elements/_text_dimensions_mixin.rb +39 -0
  22. data/motion-prime/elements/base.rb +40 -17
  23. data/motion-prime/elements/button.rb +20 -0
  24. data/motion-prime/elements/draw.rb +2 -2
  25. data/motion-prime/elements/draw/image.rb +4 -2
  26. data/motion-prime/elements/draw/label.rb +1 -1
  27. data/motion-prime/elements/error_message.rb +3 -16
  28. data/motion-prime/elements/label.rb +13 -2
  29. data/motion-prime/elements/text_field.rb +1 -0
  30. data/motion-prime/helpers/cell_section.rb +9 -0
  31. data/motion-prime/helpers/has_authorization.rb +4 -3
  32. data/motion-prime/helpers/has_normalizer.rb +20 -6
  33. data/motion-prime/helpers/has_search_bar.rb +19 -7
  34. data/motion-prime/helpers/has_style_chain_builder.rb +7 -0
  35. data/motion-prime/models/association.rb +22 -9
  36. data/motion-prime/models/association_collection.rb +54 -23
  37. data/motion-prime/models/bag.rb +13 -12
  38. data/motion-prime/models/base.rb +2 -0
  39. data/motion-prime/models/errors.rb +23 -14
  40. data/motion-prime/models/finder.rb +4 -1
  41. data/motion-prime/models/model.rb +25 -5
  42. data/motion-prime/models/store_extension.rb +1 -7
  43. data/motion-prime/models/sync.rb +75 -43
  44. data/motion-prime/mp.rb +4 -0
  45. data/motion-prime/screens/_base_mixin.rb +18 -12
  46. data/motion-prime/screens/_navigation_bar_mixin.rb +15 -6
  47. data/motion-prime/screens/_navigation_mixin.rb +15 -16
  48. data/motion-prime/screens/base_screen.rb +5 -1
  49. data/motion-prime/screens/sidebar_container_screen.rb +37 -22
  50. data/motion-prime/sections/base.rb +82 -16
  51. data/motion-prime/sections/form.rb +144 -26
  52. data/motion-prime/sections/form/base_field_section.rb +62 -29
  53. data/motion-prime/sections/form/base_header_section.rb +27 -0
  54. data/motion-prime/sections/form/date_field_section.rb +2 -17
  55. data/motion-prime/sections/form/password_field_section.rb +3 -17
  56. data/motion-prime/sections/form/select_field_section.rb +4 -35
  57. data/motion-prime/sections/form/string_field_section.rb +3 -29
  58. data/motion-prime/sections/form/submit_field_section.rb +1 -7
  59. data/motion-prime/sections/form/switch_field_section.rb +3 -23
  60. data/motion-prime/sections/form/text_field_section.rb +3 -33
  61. data/motion-prime/sections/form/text_with_button_field_section.rb +4 -40
  62. data/motion-prime/sections/tabbed.rb +25 -5
  63. data/motion-prime/sections/table.rb +86 -22
  64. data/motion-prime/sections/table/refresh_mixin.rb +3 -1
  65. data/motion-prime/styles/base.rb +7 -89
  66. data/motion-prime/styles/form.rb +116 -0
  67. data/motion-prime/support/dm_button.rb +32 -5
  68. data/motion-prime/support/dm_text_field.rb +31 -7
  69. data/motion-prime/support/dm_text_view.rb +6 -3
  70. data/motion-prime/support/dm_view_controller.rb +3 -3
  71. data/motion-prime/support/ui_search_bar_custom.rb +1 -1
  72. data/motion-prime/version.rb +1 -1
  73. data/motion-prime/views/layout.rb +18 -10
  74. data/motion-prime/views/styles.rb +19 -9
  75. data/motion-prime/views/view_builder.rb +18 -2
  76. data/motion-prime/views/view_styler.rb +59 -5
  77. data/spec/models/errors_spec.rb +3 -3
  78. data/travis.sh +3 -2
  79. metadata +28 -5
  80. data/motion-prime/elements/_text_height_mixin.rb +0 -17
  81. data/motion-prime/sections/form/table_field_section.rb +0 -51
@@ -18,17 +18,15 @@ module MotionPrime
18
18
  # @param [String] name - the name of bag
19
19
  # @return [Nil]
20
20
  def bag(name)
21
- klass = self
22
-
23
21
  define_method(name) do |*args, &block|
24
22
  return _bags[name] if _bags[name]
25
-
26
23
  bag_key = self.info[name]
27
- if bag_key.nil?
24
+ if bag_key.present?
25
+ bag = self.class.store.bagsWithKeysInArray([bag_key]).first
26
+ end
27
+ unless bag
28
28
  bag = Bag.bag
29
29
  self.info[name] = bag.key
30
- else
31
- bag = self.class.store.bagsWithKeysInArray([bag_key]).first
32
30
  end
33
31
 
34
32
  _bags[name] = bag
@@ -109,13 +107,28 @@ module MotionPrime
109
107
  self.send(bag_name).clear
110
108
  self.send(:"#{bag_name}=", value)
111
109
  end
112
- define_method("#{association_name}") do |options = {}|
110
+ define_method("#{association_name}") do |*args|
113
111
  bag = self.send(:"#{bag_name}")
114
112
  collection_options = {
115
- association_name: association_name
113
+ association_name: association_name,
114
+ inverse_relation: {
115
+ type: :has_one,
116
+ name: self.class_name_without_kvo.demodulize.underscore,
117
+ instance: self
118
+ }
116
119
  }
117
- AssociationCollection.new(bag, collection_options, options)
120
+ AssociationCollection.new(bag, collection_options, *args)
118
121
  end
119
122
  end
123
+
124
+ def belongs_to(association_name, options = {})
125
+ self._associations ||= {}
126
+ self._associations[association_name] = {
127
+ type: :belongs_to_one,
128
+ class_name: association_name.classify
129
+ }.merge(options)
130
+
131
+ self.send(:attr_accessor, association_name)
132
+ end
120
133
  end
121
134
  end
@@ -1,39 +1,70 @@
1
1
  module MotionPrime
2
2
  class AssociationCollection < ::Array
3
- attr_reader :bag
3
+ attr_reader :bag, :association_name
4
+ attr_reader :inverse_relation_name, :inverse_relation_key, :model_inverse_relation_name
4
5
 
5
6
  delegate :<<, to: :bag
6
7
 
7
- def initialize(bag, options, fetch_options = {})
8
+ def initialize(bag, options, *args)
8
9
  @bag = bag
9
10
  @association_name = options[:association_name]
10
- super(all(fetch_options))
11
- end
12
-
13
- def all(fetch_options = {})
14
- data = bag.to_a
15
- if sort_options = sort_options(fetch_options[:sort])
16
- data = data.sort do |a, b|
17
- left = []
18
- right = []
19
- sort_options.each do |key, order|
20
- if order == :desc
21
- left << b.send(key)
22
- right << a.send(key)
23
- else
24
- left << a.send(key)
25
- right << b.send(key)
26
- end
27
- end
28
- left <=> right
11
+ bag.bare_class = model_class
12
+
13
+ inverse_relation_options = options[:inverse_relation]
14
+ define_inverse_relation(inverse_relation_options)
15
+
16
+ @model_inverse_relation_name = (model_class._associations || {}).find do |name, options|
17
+ options[:class_name] == inverse_relation.class_name_without_kvo
18
+ end.try(:first)
19
+
20
+ super all(*args)
21
+ end
22
+
23
+ def new(attributes = {})
24
+ model_class.new(attributes).tap do |model|
25
+ set_inverse_relation_for(model)
26
+ end
27
+ end
28
+
29
+ def define_inverse_relation(options)
30
+ # TODO: handle different relation types (habtm, has_one...)
31
+ @inverse_relation_name = name = options[:name].to_sym
32
+ self.class_eval do
33
+ define_method name do
34
+ options[:instance]
29
35
  end
36
+ alias_method :inverse_relation, name
30
37
  end
38
+
39
+ @inverse_relation_key = inverse_relation._associations[association_name][:foreign_key].try(:to_sym)
40
+ end
41
+
42
+ def all(*args)
43
+ return [] unless bag.store.present?
44
+ data = bag.find(find_options(args[0]), sort_options(args[1]))
45
+ set_inverse_relation_for(data)
31
46
  data
32
47
  end
33
48
 
49
+ def set_inverse_relation_for(models)
50
+ [*models].each do |model|
51
+ model.send("#{inverse_relation_name}=", inverse_relation)
52
+ end if model_inverse_relation_name.present?
53
+ end
54
+
55
+ def find_options(options)
56
+ options ||= {}
57
+ options.merge!(bag_key: bag.key)
58
+ if inverse_relation_key.present?
59
+ {inverse_relation_key => inverse_relation.id}.merge options
60
+ else
61
+ options
62
+ end
63
+ end
64
+
34
65
  def sort_options(options)
35
- return options if options
36
- model_class.default_sort_options
66
+ return options if options.present?
67
+ {sort: model_class.default_sort_options}
37
68
  end
38
69
 
39
70
  def model_class
@@ -27,30 +27,30 @@ module MotionPrime
27
27
  self.savedObjects.values + self.unsavedObjects.values
28
28
  end
29
29
 
30
- # Add an object to bag
31
- #
32
- # @return self
33
- def <<(object)
34
- error_ptr = Pointer.new(:id)
35
- self.addObject(object, error:error_ptr)
36
- raise StoreError, error_ptr[0].description if error_ptr[0]
37
- self
38
- end
39
-
40
30
  # Add an object or array of objects to bag
41
31
  #
42
32
  # @return self
43
33
  def add(object_or_array)
44
34
  error_ptr = Pointer.new(:id)
45
35
  if object_or_array.is_a?(Array)
46
- self.addObjectsFromArray(object_or_array, error:error_ptr)
36
+ self.addObjectsFromArray(prepare_for_store(object_or_array), error:error_ptr)
47
37
  else
48
- self.addObject(object_or_array, error:error_ptr)
38
+ self.addObject(prepare_for_store(object_or_array), error:error_ptr)
49
39
  end
50
40
  raise StoreError, error_ptr[0].description if error_ptr[0]
51
41
  self
52
42
  end
53
43
  alias_method :+, :add
44
+ alias_method :<<, :add
45
+
46
+ def prepare_for_store(object)
47
+ if object.is_a?(Array)
48
+ object.map { |entity| prepare_for_store(entity) }
49
+ else
50
+ object.bag_key = self.key
51
+ object
52
+ end
53
+ end
54
54
 
55
55
  # Remove object from bag with key
56
56
  #
@@ -126,5 +126,6 @@ module MotionPrime
126
126
  end
127
127
 
128
128
  class NSFNanoBag
129
+ include MotionPrime::ModelFinderMethods
129
130
  include MotionPrime::BagInstanceMethods
130
131
  end
@@ -18,6 +18,8 @@ module MotionPrime
18
18
  extend MotionPrime::ModelAssociationClassMethods
19
19
  extend MotionPrime::ModelSyncClassMethods
20
20
 
21
+ attribute :bag_key # need this as we use shared store; each nested resource must belong to parent bag
22
+
21
23
  def errors
22
24
  @errors ||= Errors.new(self)
23
25
  end
@@ -1,35 +1,40 @@
1
1
  module MotionPrime
2
2
  class Errors
3
- attr_accessor :keys
3
+ attr_accessor :_unique_keys
4
4
 
5
5
  def initialize(model)
6
- @keys = []
6
+ @_unique_keys = []
7
+ @model = model
7
8
  model.class.attributes.map(&:to_sym).each do |key|
8
9
  initialize_for_key key
9
10
  end
10
11
  end
11
12
 
13
+ def unique_key(key)
14
+ [key, @model.object_id].join('_').to_sym
15
+ end
16
+
12
17
  def initialize_for_key(key)
13
- return if @keys.include?(key.to_sym)
14
- @keys << key.to_sym unless @keys.include?(key.to_sym)
15
- unless instance_variable_get("@#{key}")
16
- instance_variable_set("@#{key}", [])
17
- end
18
- self.class.send :attr_accessor, key.to_sym
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
19
24
  end
20
25
 
21
26
  def get(key)
22
27
  initialize_for_key(key)
23
- send(:"#{key.to_sym}")
28
+ send(unique_key(key))
24
29
  end
25
30
 
26
31
  def set(key, errors)
27
32
  initialize_for_key(key)
28
- send :"#{key.to_sym}=", Array.wrap(errors)
33
+ send :"#{unique_key(key)}=", Array.wrap(errors)
29
34
  end
30
35
 
31
36
  def add(key, error)
32
- get(key) << error
37
+ send(unique_key(key)) << error
33
38
  end
34
39
 
35
40
  def [](key)
@@ -40,14 +45,18 @@ module MotionPrime
40
45
  set(key, errors)
41
46
  end
42
47
 
48
+ def reset_for(key)
49
+ send :"#{unique_key(key)}=", []
50
+ end
51
+
43
52
  def reset
44
- @keys.each do |key|
45
- set(key, [])
53
+ @_unique_keys.each do |unique_key|
54
+ send :"#{unique_key}=", []
46
55
  end
47
56
  end
48
57
 
49
58
  def messages
50
- @keys.map{ |k| get(k)}.compact.flatten
59
+ @_unique_keys.map{ |uniq_k| send(uniq_k) }.compact.flatten
51
60
  end
52
61
 
53
62
  def blank?
@@ -1,5 +1,7 @@
1
1
  module MotionPrime
2
2
  module ModelFinderMethods
3
+ attr_accessor :bare_class
4
+
3
5
  # Find all models
4
6
  #
5
7
  # @return [Array] array of models
@@ -141,7 +143,8 @@ module MotionPrime
141
143
  end
142
144
 
143
145
  def bare_class_name
144
- self.to_s.split("::").last
146
+ subject = @bare_class || self
147
+ subject.to_s.split("::").last
145
148
  end
146
149
 
147
150
  private
@@ -28,8 +28,8 @@ module MotionPrime
28
28
  def assign_attributes(new_attributes, options = {})
29
29
  attributes = new_attributes.symbolize_keys
30
30
  attributes.each do |k, v|
31
- if respond_to?("#{k}=")
32
- send("#{k}=", v) unless options[:skip_nil_values] && v.nil?
31
+ if has_attribute?(k)
32
+ assign_attribute(k, v) unless options[:skip_nil_values] && v.nil?
33
33
  elsif options[:check_attribute_presence]
34
34
  puts "unknown attribute: #{k}"
35
35
  else
@@ -38,6 +38,14 @@ module MotionPrime
38
38
  end
39
39
  end
40
40
 
41
+ def assign_attribute(name, value)
42
+ self.send("#{name}=", value) if has_attribute?(name)
43
+ end
44
+
45
+ def has_attribute?(name)
46
+ respond_to?("#{name}=")
47
+ end
48
+
41
49
  def attributes_hash
42
50
  self.info.to_hash.symbolize_keys
43
51
  end
@@ -51,7 +59,7 @@ module MotionPrime
51
59
  end
52
60
 
53
61
  def model_name
54
- self.class.name.underscore
62
+ self.class_name_without_kvo.underscore
55
63
  end
56
64
 
57
65
  def inspect
@@ -97,7 +105,7 @@ module MotionPrime
97
105
  # end
98
106
  #
99
107
  # @return Nil
100
- def attribute(name)
108
+ def attribute(name, options = {})
101
109
  attributes << name
102
110
 
103
111
  define_method(name) do |*args, &block|
@@ -105,7 +113,19 @@ module MotionPrime
105
113
  end
106
114
 
107
115
  define_method((name + "=").to_sym) do |*args, &block|
108
- self.info[name] = args[0]
116
+ value = args[0]
117
+ case options[:type].to_s
118
+ when 'integer' then value = value.to_i
119
+ when 'float' then value = value.to_f
120
+ end unless value.nil?
121
+
122
+ self.info[name] = value
123
+ end
124
+
125
+ if options[:type].to_s == 'boolean'
126
+ define_method("#{name}?") do
127
+ !!self.info[name]
128
+ end
109
129
  end
110
130
  end
111
131
 
@@ -43,13 +43,7 @@ class NSFNanoStore
43
43
  raise MotionPrime::StoreError, error_ptr[0].description if error_ptr[0]
44
44
  self
45
45
  end
46
-
47
- def +(object)
48
- error_ptr = Pointer.new(:id)
49
- self.addObject(object, error:error_ptr)
50
- raise MotionPrime::StoreError, error_ptr[0].description if error_ptr[0]
51
- self
52
- end
46
+ alias_method :+, :<<
53
47
 
54
48
  # delete a object or array of objects from the array
55
49
  def delete(objects)
@@ -6,9 +6,9 @@ module MotionPrime
6
6
  base.class_attribute :_associations
7
7
  end
8
8
 
9
- def sync_url(method = :get)
9
+ def sync_url(method = :get, options = {})
10
10
  url = self.class.sync_url
11
- url = url.call(method, self) if url.is_a?(Proc)
11
+ url = url.call(method, self, options) if url.is_a?(Proc)
12
12
  normalize_sync_url(url)
13
13
  end
14
14
 
@@ -38,12 +38,13 @@ module MotionPrime
38
38
  should_fetch
39
39
  end
40
40
 
41
- method = if should_update
41
+ method = sync_options[:method]
42
+ method ||= if should_update
42
43
  persisted? ? :put : :post
43
44
  else
44
45
  :get
45
46
  end
46
- url = sync_url(method)
47
+ url = sync_url(method, sync_options)
47
48
 
48
49
  if url.blank?
49
50
  should_fetch = false
@@ -53,9 +54,9 @@ module MotionPrime
53
54
  should_fetch = !new_record? if should_fetch.nil?
54
55
  should_update ||= new_record? unless should_fetch
55
56
 
56
- fetch_with_url url do
57
+ fetch_with_url url do |data, status_code|
57
58
  save if sync_options[:save]
58
- block.call if use_callback
59
+ block.call(data, status_code) if use_callback
59
60
  end if should_fetch
60
61
 
61
62
  update_with_url url, sync_options do |data, status_code|
@@ -64,38 +65,50 @@ module MotionPrime
64
65
  block.call(data, status_code) if use_callback && !should_fetch
65
66
  end if should_update
66
67
 
67
- fetch_associations(sync_options) do
68
+ fetch_associations(sync_options) do |data, status_code|
68
69
  # run callback only if it wasn't run on fetch or update
69
- block.call if use_callback && !should_fetch && !should_update
70
+ block.call(data, status_code) if use_callback && !should_fetch && !should_update
70
71
  end if should_fetch_associations
71
72
  end
72
73
 
73
74
  # fetch from server using url
74
75
  def fetch_with_url(url, &block)
75
- api_client.get(url) do |data|
76
- if data.present?
77
- fetch_with_attributes(data, &block)
78
- end
76
+ use_callback = block_given?
77
+ api_client.get(url) do |data, status_code|
78
+ fetch_with_attributes(data, &block) if data.present?
79
+ block.call(data, status_code) if use_callback
79
80
  end
80
81
  end
81
82
 
82
83
  # update on server using url
83
84
  def update_with_url(url, sync_options = nil, &block)
84
85
  use_callback = block_given?
85
- post_data = { model_name => filtered_updatable_attributes(sync_options)}
86
- api_client.send(id ? :put : :post, url, post_data) do |data, status_code|
87
- if status_code.to_s =~ /20\d/ && data.is_a?(Hash)
88
- self.id ||= data['id']
89
- accessible_attributes = self.class.attributes.map(&:to_sym) - [:id]
90
- attrs = data.symbolize_keys.slice(*accessible_attributes)
91
- fetch_with_attributes(attrs)
86
+ filtered_attributes = filtered_updatable_attributes(sync_options)
87
+
88
+ post_data = sync_options[:params_root] || {}
89
+ post_data[:files] = {}
90
+ filtered_attributes.delete(:files).each do |file_name, file|
91
+ post_data[:files][[model_name, file_name].join] = file
92
+ end
93
+ post_data[model_name] = filtered_attributes
94
+
95
+ method = sync_options[:method] || (id ? :put : :post)
96
+ api_client.send(method, url, post_data) do |data, status_code|
97
+ update_from_response = sync_options.has_key?(:update_from_response) ? sync_options[:update_from_response] : true
98
+ if update_from_response && status_code.to_s =~ /20\d/ && data.is_a?(Hash)
99
+ set_attributes_from_response(data)
92
100
  end
93
101
  block.call(data, status_code) if use_callback
94
102
  end
95
103
  end
96
104
 
105
+ def set_attributes_from_response(data)
106
+ self.id ||= data.delete('id')
107
+ fetch_with_attributes(data)
108
+ end
109
+
97
110
  # set attributes, using fetch
98
- def fetch_with_attributes(attrs, &block)
111
+ def fetch_with_attributes(attrs)
99
112
  attrs.each do |key, value|
100
113
  if respond_to?(:"fetch_#{key}")
101
114
  self.send(:"fetch_#{key}", value)
@@ -103,14 +116,14 @@ module MotionPrime
103
116
  self.send(:"#{key}=", value)
104
117
  end
105
118
  end
106
- block.call(self) if block_given?
107
119
  end
108
120
 
109
121
  def fetch_associations(sync_options = {}, &block)
110
122
  use_callback = block_given?
111
123
  associations = self.class._associations || {}
124
+ association_keys = associations.keys.select { |key| fetch_association?(key) }
112
125
 
113
- associations.keys.each_with_index do |key, index|
126
+ association_keys.each_with_index do |key, index|
114
127
  if use_callback && associations.count - 1 == index
115
128
  fetch_association(key, sync_options, &block)
116
129
  else
@@ -119,21 +132,30 @@ module MotionPrime
119
132
  end
120
133
  end
121
134
 
135
+ def fetch_association?(key)
136
+ options = self.class._associations[key]
137
+ return if options[:if] && !options[:if].to_proc.call(self)
138
+ options[:sync_url].present?
139
+ end
140
+
122
141
  def fetch_association(key, sync_options = {}, &block)
142
+ return unless fetch_association?(key)
123
143
  options = self.class._associations[key]
124
- return unless options[:sync_url]
125
- options[:type] == :many ?
126
- fetch_has_many(key, options, sync_options, &block) :
127
- fetch_has_one(key, options, sync_options, &block)
144
+ if options[:type] == :many
145
+ fetch_has_many(key, options, sync_options, &block)
146
+ else
147
+ fetch_has_one(key, options, sync_options, &block)
148
+ end
128
149
  end
129
150
 
130
151
  def fetch_has_many(key, options = {}, sync_options = {}, &block)
131
152
  old_collection = self.send(key)
153
+
132
154
  use_callback = block_given?
133
- puts "SYNC: started sync for #{key} in #{self.class.name}"
134
- api_client.get normalize_sync_url(options[:sync_url]) do |data|
155
+ puts "SYNC: started sync for #{key} in #{self.class_name_without_kvo}"
156
+ api_client.get normalize_sync_url(options[:sync_url]) do |data, status_code|
135
157
  data = data[options[:sync_key]] if options[:sync_key]
136
- if data.present?
158
+ if data
137
159
  # Update/Create existing records
138
160
  data.each do |attributes|
139
161
  model = old_collection.detect{ |model| model.id == attributes[:id]}
@@ -151,19 +173,19 @@ module MotionPrime
151
173
  end
152
174
  end
153
175
  save if sync_options[:save]
154
- puts "SYNC: finished sync for #{key} in #{self.class.name}"
155
- block.call if use_callback
176
+ puts "SYNC: finished sync for #{key} in #{self.class_name_without_kvo}"
177
+ block.call(data, status_code) if use_callback
156
178
  else
157
- puts "SYNC ERROR: failed sync for #{key} in #{self.class.name}"
158
- block.call if use_callback
179
+ puts "SYNC ERROR: failed sync for #{key} in #{self.class_name_without_kvo}"
180
+ block.call(data, status_code) if use_callback
159
181
  end
160
182
  end
161
183
  end
162
184
 
163
185
  def fetch_has_one(key, options = {}, &block)
164
186
  use_callback = block_given?
165
- puts "SYNC: started sync for #{key} in #{self.class.name}"
166
- api_client.get normalize_sync_url(options[:sync_url]) do |data|
187
+ puts "SYNC: started sync for #{key} in #{self.class_name_without_kvo}"
188
+ api_client.get normalize_sync_url(options[:sync_url]) do |data, status_code|
167
189
  data = data[options[:sync_key]] if options[:sync_key]
168
190
  if data.present?
169
191
  model = self.send(key)
@@ -173,10 +195,10 @@ module MotionPrime
173
195
  end
174
196
  model.fetch_with_attributes(data)
175
197
  model.save if sync_options[:save]
176
- block.call if use_callback
198
+ block.call(data, status_code) if use_callback
177
199
  else
178
- puts "SYNC ERROR: failed sync for #{key} in #{self.class.name}"
179
- block.call if use_callback
200
+ puts "SYNC ERROR: failed sync for #{key} in #{self.class_name_without_kvo}"
201
+ block.call(data, status_code) if use_callback
180
202
  end
181
203
  end
182
204
  end
@@ -186,19 +208,29 @@ module MotionPrime
186
208
  updatable_attributes = self.class.updatable_attributes
187
209
 
188
210
  if updatable_attributes.blank?
189
- return slice_attributes ? attributes_hash.slice(*slice_attributes) : attributes_hash
211
+ attrs = slice_attributes ? attributes_hash.slice(*slice_attributes) : attributes_hash
212
+ return attrs.merge(files: {})
190
213
  end
191
214
 
192
215
  updatable_attributes = updatable_attributes.slice(*slice_attributes) if slice_attributes
193
- updatable_attributes.to_a.inject({}) do |hash, attribute|
216
+ updatable_attributes.to_a.inject({files: {}}) do |hash, attribute|
194
217
  key, options = *attribute
195
- return hash if options[:if] && !send(options[:if])
218
+ next hash if options[:if] && !send(options[:if])
196
219
  value = if block = options[:block]
197
- block.call(self)
220
+ block.call(self, hash)
198
221
  else
199
222
  info[key]
200
223
  end
201
- hash.merge(key => value)
224
+
225
+ if key.to_s.starts_with?('file_')
226
+ value.to_a.each do |file_data|
227
+ file_name, file = file_data.to_a
228
+ hash[:files]["[#{key.partition('_').last}]#{file_name}"] = file
229
+ end
230
+ else
231
+ hash.merge!(key => value)
232
+ end
233
+ hash
202
234
  end
203
235
  end
204
236