motion-prime 0.1.7 → 0.2.0

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