motion-prime 0.1.7 → 0.2.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 (46) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGELOG.md +5 -0
  3. data/Gemfile.lock +1 -1
  4. data/files/app/config/base.rb +7 -4
  5. data/files/app/styles/sidebar.rb +4 -4
  6. data/lib/motion-prime.rb +1 -0
  7. data/motion-prime/config/base.rb +5 -0
  8. data/motion-prime/elements/_text_height_mixin.rb +4 -4
  9. data/motion-prime/elements/base.rb +11 -5
  10. data/motion-prime/elements/draw.rb +105 -26
  11. data/motion-prime/elements/draw/image.rb +2 -2
  12. data/motion-prime/elements/draw/label.rb +20 -3
  13. data/motion-prime/elements/error_message.rb +23 -0
  14. data/motion-prime/elements/label.rb +8 -0
  15. data/motion-prime/models/association.rb +0 -1
  16. data/motion-prime/models/base.rb +8 -192
  17. data/motion-prime/models/errors.rb +62 -1
  18. data/motion-prime/models/exceptions.rb +3 -0
  19. data/motion-prime/models/model.rb +33 -1
  20. data/motion-prime/models/sync.rb +233 -0
  21. data/motion-prime/screens/_base_mixin.rb +4 -1
  22. data/motion-prime/screens/_navigation_mixin.rb +5 -5
  23. data/motion-prime/sections/base.rb +18 -5
  24. data/motion-prime/sections/form.rb +75 -16
  25. data/motion-prime/sections/form/base_field_section.rb +52 -1
  26. data/motion-prime/sections/form/select_field_section.rb +14 -1
  27. data/motion-prime/sections/form/string_field_section.rb +20 -5
  28. data/motion-prime/sections/form/switch_field_section.rb +33 -0
  29. data/motion-prime/sections/form/table_field_section.rb +40 -0
  30. data/motion-prime/sections/form/text_field_section.rb +23 -6
  31. data/motion-prime/sections/table.rb +25 -8
  32. data/motion-prime/styles/base.rb +31 -4
  33. data/motion-prime/support/dm_button.rb +3 -2
  34. data/motion-prime/version.rb +1 -1
  35. data/motion-prime/views/layout.rb +1 -1
  36. data/motion-prime/views/view_builder.rb +82 -66
  37. data/motion-prime/views/view_styler.rb +2 -0
  38. data/resources/fonts/ubuntu.ttf +0 -0
  39. data/spec/helpers/models.rb +1 -1
  40. data/spec/models/association_spec.rb +1 -1
  41. data/spec/models/errors_spec.rb +29 -0
  42. data/spec/models/finder_spec.rb +1 -1
  43. data/spec/models/{base_model_spec.rb → model_spec.rb} +30 -1
  44. data/spec/models/store_extension_spec.rb +1 -1
  45. data/spec/models/store_spec.rb +1 -1
  46. metadata +12 -4
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MjdhZjg1NjJhYTYxNGZlMTA1ZWNkZGQ0ZmUwZTg3YzdiZjBkM2MyNw==
4
+ ZTdmYjFmM2NhYWE3ZGQyNzFjYWZjYjg2NzUzOTFkMTkwZmVjYTBjMw==
5
5
  data.tar.gz: !binary |-
6
- Mjg3NjUzNDE3ZTA1ZDVkZmZmZWMwNDUwMmMxZjlhNDczN2I5NWVjNg==
6
+ NmI1OTFjNGRmYmZjOTYyNWExOGJlMjQ4YjVjYzc4M2M0Y2M3ZWQ4Zg==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- MWVjMjhiMThkMjk3N2M1NWQ5MzU4ZjBjMDc3MWY5MmFiYzEwMWFlN2NlNzE0
10
- OTM0NzM5NDMwZDMxNjhlODAzNjdiN2I3MmMyMGFlNDhiYjhjZmQ3MjFlOGI5
11
- NjkzZTM5ZTQzZDNhMDMyZjgyYzg4MmUzYjYyZDI5ZGUzMjY1OWI=
9
+ ODdiZTc3ZmIyM2ZlZDJhOTg1YTU2OGEyODFhZWE5MGI2NWUzY2ZiYTQ5NzU5
10
+ ZGFlYmRkZDllYjY4MjA4YjA3YTE5YmZlNmE4NjU5Zjk3OTVhNTJkM2IwMDI4
11
+ NGIzYTRjYzg5MzBhMmM1NjY0NmJmY2U4MWMzN2JiODczZWNmMjY=
12
12
  data.tar.gz: !binary |-
13
- MDY1MzIwNjJlOThmN2Q2NWZhZjI0YjJkMDZlMzBhNDI2MTc3MjZhNmYzMmRj
14
- MGI2ODFhNWVlY2MyMjBhNTc4M2VjN2E2ZmM2ZWJlZDE1MGM2ODVmOTE1NjNk
15
- YjNhZGYwNjZiZDRkMzQwZmIyYzBhMTRlNGFjZjdjODAxMzcyNzM=
13
+ MjBiMDY2NTg1ZWM1OGU3ZDUzYTcyYmY5YTgyMWVlNmE2M2EzYTE0YjVmYjBk
14
+ YzExOTI5ZDQ3MDZlN2Q1MDkwNjE0ZTU0OTRlM2Q1NjA2Y2ZmNThmZmUxNzUy
15
+ OThlMzU4NTI5ZDJkOTIwMmE3MDA2OTZkM2I2OTAwZDViY2ZmMzU=
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ === 0.2.0
2
+ * MP::BaseModel improvements
3
+ * Added ability to observe form field errors
4
+ * Use MP::Config for style configurations
5
+
1
6
  === 0.1.7
2
7
  * MP::BaseModel#fetch_associations now supports callback option
3
8
  * MP::LabelDrawElement now supports `size_to_fit` option
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- motion-prime (0.1.7)
4
+ motion-prime (0.2.0)
5
5
  bubble-wrap
6
6
  cocoapods
7
7
  motion-cocoapods
@@ -1,6 +1,9 @@
1
- APP_CONFIG = {}
2
- APP_CONFIG[:css_color_base] = 0x3aa9b6
3
- APP_CONFIG[:css_color_dark] = 0x41929c
1
+ MP = MotionPrime unless defined?(MP)
2
+
3
+ MP::Config.color do |color|
4
+ color.base = 0x3aa9b6
5
+ color.dark = 0x41929c
6
+ end
4
7
 
5
8
  # setup model's store
6
- MotionPrime::Store.connect
9
+ MP::Store.connect
@@ -2,7 +2,7 @@ MotionPrime::Styles.define :sidebar do
2
2
  # navigation layout
3
3
  # ----------
4
4
  style :screen,
5
- background_color: APP_CONFIG[:css_color_base],
5
+ background_color: MP::Config.color.base,
6
6
  opaque: true
7
7
 
8
8
  style :table,
@@ -10,8 +10,8 @@ MotionPrime::Styles.define :sidebar do
10
10
  left: 0,
11
11
  width: 320,
12
12
  bottom: 0,
13
- background_color: APP_CONFIG[:css_color_base],
14
- separator_color: APP_CONFIG[:css_color_dark],
13
+ background_color: MP::Config.color.base,
14
+ separator_color: MP::Config.color.dark,
15
15
  opaque: true
16
16
 
17
17
  style :table_cell,
@@ -19,7 +19,7 @@ MotionPrime::Styles.define :sidebar do
19
19
  opaque: true
20
20
 
21
21
  style :action_title,
22
- background_color: APP_CONFIG[:css_color_base],
22
+ background_color: MP::Config.color.base,
23
23
  text_color: :white,
24
24
  top: 10,
25
25
  width: 320,
data/lib/motion-prime.rb CHANGED
@@ -4,4 +4,5 @@ Motion::Require.all(Dir.glob(File.expand_path('../../motion-prime/**/*.rb', __FI
4
4
 
5
5
  Motion::Project::App.setup do |app|
6
6
  app.detect_dependencies = false
7
+ app.fonts = [File.expand_path('../../resources/fonts/ubuntu.ttf', __FILE__)]
7
8
  end
@@ -5,4 +5,9 @@ MotionPrime::Config.model do |model|
5
5
  else
6
6
  model.store_type = :file
7
7
  end
8
+ end
9
+ MotionPrime::Config.font.name = "Ubuntu"
10
+ MotionPrime::Config.color do |color|
11
+ color.base = 0x424242
12
+ color.error = 0xef471f
8
13
  end
@@ -1,6 +1,7 @@
1
1
  module MotionPrime
2
2
  module ElementTextHeightMixin
3
- def height
3
+ def content_height
4
+ return 0 if computed_options[:text].blank?
4
5
  width = computed_options[:width]
5
6
  font = computed_options[:font] || :system.uifont
6
7
  raise "Please set element width for height calculation" unless width
@@ -9,9 +10,8 @@ module MotionPrime
9
10
  lineBreakMode: UILineBreakModeWordWrap).height
10
11
  end
11
12
 
12
- def outer_height
13
- height + computed_options[:top].to_i +
14
- computed_options[:bottom].to_i
13
+ def content_outer_height
14
+ content_height + computed_inner_top + computed_inner_bottom
15
15
  end
16
16
  end
17
17
  end
@@ -8,13 +8,14 @@ module MotionPrime
8
8
  include HasNormalizer
9
9
 
10
10
  attr_accessor :options, :section, :name,
11
- :view_class, :view, :styles, :screen
11
+ :view_class, :view, :view_name, :styles, :screen
12
12
 
13
13
  define_callbacks :render
14
14
 
15
15
  def initialize(options = {})
16
16
  @options = options
17
17
  @section = options.delete(:section)
18
+ @observe_errors_for = options.delete(:observe_errors_for)
18
19
  @name = options[:name]
19
20
  @block = options.delete(:block)
20
21
  @view_class = options.delete(:view_class) || "UIView"
@@ -39,7 +40,7 @@ module MotionPrime
39
40
  end
40
41
 
41
42
  def compute_options!
42
- @computed_options = options
43
+ @computed_options = normalize_options(options, section)
43
44
  compute_block_options
44
45
  compute_style_options
45
46
  @computed_options = normalize_options(@computed_options, section)
@@ -56,9 +57,14 @@ module MotionPrime
56
57
  end
57
58
 
58
59
  def compute_style_options
59
- @styles = [:"base_#{@view_name}"]
60
- @styles += Array.wrap(@computed_options.delete(:styles))
61
- @styles += [:"#{section.name}_#{name}"] if section.present?
60
+ @styles = []
61
+ @styles << :"#{section.name}_#{name}" if section.present?
62
+ @styles << :"base_#{@view_name}"
63
+ if section && @observe_errors_for && @observe_errors_for.errors[section.name].present?
64
+ @styles << :"base_#{name}_with_errors"
65
+ end
66
+ custom_styles = @computed_options.delete(:styles)
67
+ @styles += [*custom_styles]
62
68
  @computed_options.merge!(style_options)
63
69
  end
64
70
 
@@ -18,46 +18,125 @@ module MotionPrime
18
18
  view.bounds.size.height
19
19
  end
20
20
 
21
+ def computed_padding_left
22
+ computed_options[:padding_left] ||
23
+ computed_options[:padding] || 0
24
+ end
25
+
26
+ def computed_padding_right
27
+ computed_options[:padding_right] ||
28
+ computed_options[:padding] || 0
29
+ end
30
+
31
+ def computed_padding_top
32
+ computed_options[:padding_top] ||
33
+ computed_options[:padding] || 0
34
+ end
35
+
36
+ def computed_padding_bottom
37
+ computed_options[:padding_bottom] ||
38
+ computed_options[:padding] || 0
39
+ end
40
+
21
41
  def computed_width
22
- width = computed_options[:width]
23
- width = 0.0 if width.nil?
24
-
25
- # calculate width if width is relative, e.g 0.7
26
- if width > 0 && width <= 1
27
- width * computed_max_width
28
- else
29
- width > computed_max_width ? computed_max_width : width
42
+ @computed_width ||= begin
43
+ width = computed_options[:width]
44
+ width = 0.0 if width.nil?
45
+
46
+ # calculate width if width is relative, e.g 0.7
47
+ if width > 0 && width <= 1
48
+ width * computed_max_width
49
+ else
50
+ width > computed_max_width ? computed_max_width : width
51
+ end
52
+ end
53
+ end
54
+
55
+ # content width + content padding
56
+ def computed_outer_width
57
+ @computed_outer_width ||= begin
58
+ computed_width + computed_padding_left + computed_padding_right
30
59
  end
31
60
  end
32
61
 
33
62
  def computed_height
34
- height = computed_options[:height]
35
- height = 0.0 if height.nil?
36
-
37
- # calculate height if height is relative, e.g 0.7
38
- if height > 0 && height <= 1
39
- height * computed_max_height
40
- else
41
- height > computed_max_height ? computed_max_height : height
63
+ @computed_height ||= begin
64
+ height = computed_options[:height]
65
+ height = 0.0 if height.nil?
66
+
67
+ # calculate height if height is relative, e.g 0.7
68
+ if height > 0 && height <= 1
69
+ height * computed_max_height
70
+ else
71
+ height > computed_max_height ? computed_max_height : height
72
+ end
73
+ end
74
+ end
75
+
76
+ # content height + content padding
77
+ def computed_outer_height
78
+ @computed_outer_height ||= begin
79
+ computed_height + computed_padding_top + computed_padding_bottom
42
80
  end
43
81
  end
44
82
 
45
83
  def computed_left
46
- left = computed_options[:left]
47
- right = computed_options[:right]
48
- return left if left
49
- return 0 if right.nil?
84
+ @computed_left ||= begin
85
+ left = computed_options[:left]
86
+ right = computed_options[:right]
87
+ return left if left
88
+ return 0 if right.nil?
50
89
 
51
- computed_max_width - (computed_width + right)
90
+ computed_max_width - (computed_width + right)
91
+ end
92
+ end
93
+
94
+ # content left + content padding
95
+ def computed_inner_left
96
+ @computed_inner_left ||= begin
97
+ computed_left + computed_padding_left
98
+ end
52
99
  end
53
100
 
54
101
  def computed_top
55
- top = computed_options[:top]
56
- bottom = computed_options[:bottom]
57
- return top if top
58
- return 0 if bottom.nil?
102
+ @computed_top ||= begin
103
+ top = computed_options[:top]
104
+ bottom = computed_options[:bottom]
105
+ return top if top
106
+ return 0 if bottom.nil?
107
+
108
+ computed_max_height - (computed_height + bottom)
109
+ end
110
+ end
59
111
 
60
- computed_max_height - (computed_height + bottom)
112
+ # content top + content padding
113
+ def computed_inner_top
114
+ @computed_inner_top ||= begin
115
+ computed_top + computed_padding_top
116
+ end
117
+ end
118
+
119
+ def computed_bottom
120
+ computed_options[:bottom] || 0
121
+ end
122
+
123
+ def computed_inner_bottom
124
+ computed_bottom + computed_padding_bottom
125
+ end
126
+
127
+ def computed_right
128
+ computed_options[:right] || 0
129
+ end
130
+
131
+ def computed_inner_right
132
+ computed_right + computed_padding_right
133
+ end
134
+
135
+ def reset_computed_values
136
+ [:left, :top, :right, :bottom, :width, :height].each do |key|
137
+ instance_variable_set "@compited_#{key}", nil
138
+ instance_variable_set "@compited_inner_#{key}", nil
139
+ end
61
140
  end
62
141
 
63
142
  class << self
@@ -7,8 +7,8 @@ module MotionPrime
7
7
  image_rect = CGRectMake(
8
8
  computed_left,
9
9
  computed_top,
10
- computed_options[:width],
11
- computed_options[:height]
10
+ computed_width,
11
+ computed_height
12
12
  )
13
13
  # draw already initialized image
14
14
  if image_data
@@ -7,10 +7,19 @@ module MotionPrime
7
7
  options = computed_options
8
8
  return if options[:hidden]
9
9
 
10
- if options[:size_to_fit]
11
- @computed_options[:height] = outer_height
10
+ size_to_fit_if_needed
11
+
12
+ # render background
13
+ bg_color = options[:background_color]
14
+ if bg_color
15
+ rect = CGRectMake(
16
+ computed_left, computed_top, computed_outer_width, computed_outer_height
17
+ )
18
+ bg_color.uicolor.setFill
19
+ UIRectFill(rect)
12
20
  end
13
21
 
22
+ # render text
14
23
  color = options[:text_color]
15
24
  color.uicolor.set if color
16
25
  font = options[:font] || :system
@@ -21,7 +30,8 @@ module MotionPrime
21
30
  )
22
31
  else
23
32
  rect = CGRectMake(
24
- computed_left, computed_top, computed_width, computed_height
33
+ computed_inner_left, computed_inner_top,
34
+ computed_width, computed_height
25
35
  )
26
36
  line_break = options.has_key?(:line_break_mode) ? options[:line_break_mode] : :wordwrap
27
37
  alignment = options.has_key?(:text_alignment) ? options[:text_alignment] : :left
@@ -32,5 +42,12 @@ module MotionPrime
32
42
  )
33
43
  end
34
44
  end
45
+
46
+ def size_to_fit_if_needed
47
+ if computed_options[:size_to_fit]
48
+ @computed_options[:height] = content_height
49
+ reset_computed_values
50
+ end
51
+ end
35
52
  end
36
53
  end
@@ -0,0 +1,23 @@
1
+ module MotionPrime
2
+ class ErrorMessageElement < BaseElement
3
+ include MotionPrime::ElementTextHeightMixin
4
+
5
+ after_render :size_to_fit
6
+
7
+ def view_class
8
+ "UILabel"
9
+ end
10
+
11
+ def size_to_fit
12
+ view.size.height = self.content_height
13
+ end
14
+
15
+ def computed_inner_top
16
+ computed_options[:top].to_i
17
+ end
18
+
19
+ def computed_inner_bottom
20
+ computed_options[:bottom].to_i
21
+ end
22
+ end
23
+ end
@@ -9,5 +9,13 @@ module MotionPrime
9
9
  view.sizeToFit
10
10
  end
11
11
  end
12
+
13
+ def computed_inner_top
14
+ computed_options[:top].to_i
15
+ end
16
+
17
+ def computed_inner_bottom
18
+ computed_options[:bottom].to_i
19
+ end
12
20
  end
13
21
  end
@@ -13,7 +13,6 @@ module MotionPrime
13
13
  end
14
14
  end
15
15
  module ModelAssociationClassMethods
16
-
17
16
  # Defines bag associated with model, creates accessor for bag
18
17
  #
19
18
  # @param [String] name - the name of bag
@@ -3,207 +3,23 @@ motion_require './bag.rb'
3
3
  motion_require './finder.rb'
4
4
  motion_require './model.rb'
5
5
  motion_require './store.rb'
6
+ motion_require './sync.rb'
7
+ motion_require './association.rb'
6
8
  motion_require './store_extension.rb'
7
9
  module MotionPrime
8
10
  class BaseModel < NSFNanoObject
9
- class_attribute :_sync_url
10
- class_attribute :_updatable_attributes
11
- class_attribute :_associations
12
- alias_method :attributes, :info
13
11
  include MotionPrime::HasAuthorization
14
-
15
12
  include MotionPrime::ModelMethods
16
- extend MotionPrime::ModelClassMethods
17
-
18
- extend MotionPrime::ModelFinderMethods
19
13
  include MotionPrime::ModelAssociationMethods
14
+ include MotionPrime::ModelSyncMethods
20
15
 
16
+ extend MotionPrime::ModelClassMethods
17
+ extend MotionPrime::ModelFinderMethods
21
18
  extend MotionPrime::ModelAssociationClassMethods
19
+ extend MotionPrime::ModelSyncClassMethods
22
20
 
23
- def sync_url
24
- normalize_sync_url(self.class.sync_url)
25
- end
26
-
27
- def model_name
28
- self.class.name.underscore
29
- end
30
-
31
- def new_record?
32
- id.blank?
33
- end
34
-
35
- # destroy on server and delete on local
36
- def destroy(&block)
37
- use_callback = block_given?
38
- api_client.delete(sync_url) do
39
- block.call() if use_callback
40
- end
41
- delete
42
- end
43
-
44
- # sync with server and save on local
45
- def sync!(sync_options = {}, &block)
46
- sync(sync_options.merge(save: true), &block)
47
- end
48
-
49
- # sync with with server
50
- # TODO: order of fetch/update should be based on updated time
51
- def sync(sync_options = {}, &block)
52
- use_callback = block_given?
53
- should_fetch = sync_options[:fetch]
54
- should_update = sync_options[:update]
55
-
56
- should_fetch = false if sync_url.blank?
57
- should_update = false if sync_url.blank?
58
-
59
- should_fetch = !new_record? if should_fetch.nil?
60
- should_update = new_record? if should_update.nil?
61
-
62
- fetch_with_url self.sync_url do
63
- save if sync_options[:save]
64
- block.call if use_callback
65
- end if should_fetch
66
- update_with_url self.sync_url do
67
- save if sync_options[:save]
68
-
69
- # run callback only if it wasn't run on fetch
70
- block.call if use_callback && !should_fetch
71
- end if should_update
72
-
73
- fetch_associations(sync_options) do
74
- # run callback only if it wasn't run on fetch or update
75
- block.call if use_callback && !should_fetch && !should_update
76
- end
77
- end
78
-
79
- # fetch from server using url
80
- def fetch_with_url(url, &block)
81
- api_client.get(url) do |data|
82
- if data.present?
83
- fetch_with_attributes(data, &block)
84
- end
85
- end
86
- end
87
-
88
- # update on server using url
89
- def update_with_url(url, &block)
90
- use_callback = block_given?
91
- post_data = { model_name => filtered_updatable_attributes}
92
- api_client.send(id ? :put : :post, url, post_data) do |data|
93
- self.id ||= data['id']
94
- block.call() if use_callback
95
- end
96
- end
97
-
98
- # set attributes, using fetch
99
- def fetch_with_attributes(attrs, &block)
100
- attrs.each do |key, value|
101
- if respond_to?(:"fetch_#{key}")
102
- self.send(:"fetch_#{key}", value)
103
- elsif respond_to?(:"#{key}=")
104
- self.send(:"#{key}=", value)
105
- end
106
- end
107
- block.call(self) if block_given?
108
- end
109
-
110
- def fetch_associations(sync_options = {}, &block)
111
- use_callback = block_given?
112
- associations = self.class._associations || {}
113
-
114
- associations.keys.each_with_index do |key, index|
115
- if use_callback && associations.count - 1 == index
116
- fetch_association(key, sync_options, &block)
117
- else
118
- fetch_association(key, sync_options)
119
- end
120
- end
121
- end
122
-
123
- def fetch_association(key, sync_options = {}, &block)
124
- options = self.class._associations[key]
125
- return unless options[:sync_url]
126
- options[:type] == :many ?
127
- fetch_has_many(key, options, sync_options, &block) :
128
- fetch_has_one(key, options, sync_options, &block)
129
- end
130
-
131
- def fetch_has_many(key, options = {}, sync_options = {}, &block)
132
- old_collection = self.send(key)
133
- use_callback = block_given?
134
- puts "SYNC: started sync for #{key} in #{self.class.name}"
135
- api_client.get normalize_sync_url(options[:sync_url]) do |data|
136
- data = data[options[:sync_key]] if options[:sync_key]
137
- if data.present?
138
- # Update/Create existing records
139
- data.each do |attributes|
140
- model = old_collection.detect{ |model| model.id == attributes[:id]}
141
- unless model
142
- model = key.singularize.to_s.classify.constantize.new
143
- self.send(:"#{key}_bag") << model
144
- end
145
- model.fetch_with_attributes(attributes)
146
- model.save if sync_options[:save]
147
- end
148
- old_collection.each do |old_model|
149
- model = data.detect{ |model| model[:id] == old_model.id}
150
- unless model
151
- old_model.delete
152
- end
153
- end
154
- save if sync_options[:save]
155
- puts "SYNC: finished sync for #{key} in #{self.class.name}"
156
- block.call if use_callback
157
- else
158
- puts "SYNC ERROR: failed sync for #{key} in #{self.class.name}"
159
- block.call if use_callback
160
- end
161
- end
162
- end
163
-
164
- def fetch_has_one(key, options = {}, &block)
165
- # TODO: add implementation
166
- end
167
-
168
- def inspect
169
- "#<#{self.class}:0x#{self.object_id.to_s(16)}> " + MotionPrime::JSON.generate(attributes)
170
- end
171
-
172
- def filtered_updatable_attributes
173
- return attributes if self.class.updatable_attributes.blank?
174
- self.class.updatable_attributes.to_a.inject({}) do |hash, attribute|
175
- key, options = *attribute
176
- return hash if options[:if] && !send(options[:if])
177
- if block = options[:block]
178
- value = instance_eval(&block)
179
- else
180
- value = attributes[key]
181
- end
182
- hash.merge!(key => value)
183
- end
184
- end
185
-
186
- def normalize_sync_url(url)
187
- url.to_s.gsub(':id', id.to_s)
188
- end
189
-
190
- class << self
191
- def sync_url(url = nil)
192
- url ? self._sync_url = url : self._sync_url
193
- end
194
-
195
- def updatable_attributes(*attrs)
196
- return self._updatable_attributes if attrs.blank?
197
- attrs.each do |attribute|
198
- updatable_attribute attribute
199
- end
200
- end
201
-
202
- def updatable_attribute(attribute, options = {}, &block)
203
- options[:block] = block if block_given?
204
- self._updatable_attributes ||= {}
205
- self._updatable_attributes[attribute] = options
206
- end
21
+ def errors
22
+ @errors ||= Errors.new(self)
207
23
  end
208
24
  end
209
25
  end