motion-prime 0.2.1 → 0.3.0

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