active_data 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.rspec +0 -1
  4. data/.rvmrc +1 -1
  5. data/.travis.yml +13 -6
  6. data/Appraisals +7 -0
  7. data/Gemfile +1 -5
  8. data/Guardfile +68 -15
  9. data/README.md +144 -2
  10. data/active_data.gemspec +19 -11
  11. data/gemfiles/rails.4.0.gemfile +14 -0
  12. data/gemfiles/rails.4.1.gemfile +14 -0
  13. data/gemfiles/rails.4.2.gemfile +14 -0
  14. data/gemfiles/rails.5.0.gemfile +14 -0
  15. data/lib/active_data.rb +120 -3
  16. data/lib/active_data/active_record/associations.rb +50 -0
  17. data/lib/active_data/active_record/nested_attributes.rb +24 -0
  18. data/lib/active_data/config.rb +40 -0
  19. data/lib/active_data/errors.rb +93 -0
  20. data/lib/active_data/extensions.rb +33 -0
  21. data/lib/active_data/model.rb +16 -74
  22. data/lib/active_data/model/associations.rb +84 -15
  23. data/lib/active_data/model/associations/base.rb +79 -0
  24. data/lib/active_data/model/associations/collection/embedded.rb +12 -0
  25. data/lib/active_data/model/associations/collection/proxy.rb +32 -0
  26. data/lib/active_data/model/associations/collection/referenced.rb +26 -0
  27. data/lib/active_data/model/associations/embeds_many.rb +124 -18
  28. data/lib/active_data/model/associations/embeds_one.rb +90 -15
  29. data/lib/active_data/model/associations/nested_attributes.rb +180 -0
  30. data/lib/active_data/model/associations/references_many.rb +96 -0
  31. data/lib/active_data/model/associations/references_one.rb +83 -0
  32. data/lib/active_data/model/associations/reflections/base.rb +100 -0
  33. data/lib/active_data/model/associations/reflections/embeds_many.rb +25 -0
  34. data/lib/active_data/model/associations/reflections/embeds_one.rb +49 -0
  35. data/lib/active_data/model/associations/reflections/reference_reflection.rb +45 -0
  36. data/lib/active_data/model/associations/reflections/references_many.rb +28 -0
  37. data/lib/active_data/model/associations/reflections/references_one.rb +28 -0
  38. data/lib/active_data/model/associations/validations.rb +63 -0
  39. data/lib/active_data/model/attributes.rb +247 -0
  40. data/lib/active_data/model/attributes/attribute.rb +73 -0
  41. data/lib/active_data/model/attributes/base.rb +116 -0
  42. data/lib/active_data/model/attributes/collection.rb +17 -0
  43. data/lib/active_data/model/attributes/dictionary.rb +26 -0
  44. data/lib/active_data/model/attributes/localized.rb +42 -0
  45. data/lib/active_data/model/attributes/reference_many.rb +21 -0
  46. data/lib/active_data/model/attributes/reference_one.rb +42 -0
  47. data/lib/active_data/model/attributes/reflections/attribute.rb +55 -0
  48. data/lib/active_data/model/attributes/reflections/base.rb +62 -0
  49. data/lib/active_data/model/attributes/reflections/collection.rb +10 -0
  50. data/lib/active_data/model/attributes/reflections/dictionary.rb +13 -0
  51. data/lib/active_data/model/attributes/reflections/localized.rb +43 -0
  52. data/lib/active_data/model/attributes/reflections/reference_many.rb +10 -0
  53. data/lib/active_data/model/attributes/reflections/reference_one.rb +58 -0
  54. data/lib/active_data/model/attributes/reflections/represents.rb +55 -0
  55. data/lib/active_data/model/attributes/represents.rb +64 -0
  56. data/lib/active_data/model/callbacks.rb +71 -0
  57. data/lib/active_data/model/conventions.rb +35 -0
  58. data/lib/active_data/model/dirty.rb +77 -0
  59. data/lib/active_data/model/lifecycle.rb +307 -0
  60. data/lib/active_data/model/localization.rb +21 -0
  61. data/lib/active_data/model/persistence.rb +57 -0
  62. data/lib/active_data/model/primary.rb +51 -0
  63. data/lib/active_data/model/scopes.rb +77 -0
  64. data/lib/active_data/model/validations.rb +27 -0
  65. data/lib/active_data/model/validations/associated.rb +19 -0
  66. data/lib/active_data/model/validations/nested.rb +39 -0
  67. data/lib/active_data/railtie.rb +7 -0
  68. data/lib/active_data/version.rb +1 -1
  69. data/spec/lib/active_data/active_record/associations_spec.rb +149 -0
  70. data/spec/lib/active_data/active_record/nested_attributes_spec.rb +16 -0
  71. data/spec/lib/active_data/config_spec.rb +44 -0
  72. data/spec/lib/active_data/model/associations/embeds_many_spec.rb +362 -52
  73. data/spec/lib/active_data/model/associations/embeds_one_spec.rb +250 -31
  74. data/spec/lib/active_data/model/associations/nested_attributes_spec.rb +23 -0
  75. data/spec/lib/active_data/model/associations/references_many_spec.rb +196 -0
  76. data/spec/lib/active_data/model/associations/references_one_spec.rb +134 -0
  77. data/spec/lib/active_data/model/associations/reflections/embeds_many_spec.rb +144 -0
  78. data/spec/lib/active_data/model/associations/reflections/embeds_one_spec.rb +116 -0
  79. data/spec/lib/active_data/model/associations/reflections/references_many_spec.rb +255 -0
  80. data/spec/lib/active_data/model/associations/reflections/references_one_spec.rb +208 -0
  81. data/spec/lib/active_data/model/associations/validations_spec.rb +153 -0
  82. data/spec/lib/active_data/model/associations_spec.rb +189 -0
  83. data/spec/lib/active_data/model/attributes/attribute_spec.rb +144 -0
  84. data/spec/lib/active_data/model/attributes/base_spec.rb +82 -0
  85. data/spec/lib/active_data/model/attributes/collection_spec.rb +73 -0
  86. data/spec/lib/active_data/model/attributes/dictionary_spec.rb +93 -0
  87. data/spec/lib/active_data/model/attributes/localized_spec.rb +88 -33
  88. data/spec/lib/active_data/model/attributes/reflections/attribute_spec.rb +72 -0
  89. data/spec/lib/active_data/model/attributes/reflections/base_spec.rb +56 -0
  90. data/spec/lib/active_data/model/attributes/reflections/collection_spec.rb +37 -0
  91. data/spec/lib/active_data/model/attributes/reflections/dictionary_spec.rb +43 -0
  92. data/spec/lib/active_data/model/attributes/reflections/localized_spec.rb +37 -0
  93. data/spec/lib/active_data/model/attributes/reflections/represents_spec.rb +70 -0
  94. data/spec/lib/active_data/model/attributes/represents_spec.rb +153 -0
  95. data/spec/lib/active_data/model/attributes_spec.rb +243 -0
  96. data/spec/lib/active_data/model/callbacks_spec.rb +338 -0
  97. data/spec/lib/active_data/model/conventions_spec.rb +12 -0
  98. data/spec/lib/active_data/model/dirty_spec.rb +75 -0
  99. data/spec/lib/active_data/model/lifecycle_spec.rb +330 -0
  100. data/spec/lib/active_data/model/nested_attributes.rb +202 -0
  101. data/spec/lib/active_data/model/persistence_spec.rb +47 -0
  102. data/spec/lib/active_data/model/primary_spec.rb +84 -0
  103. data/spec/lib/active_data/model/scopes_spec.rb +88 -0
  104. data/spec/lib/active_data/model/typecasting_spec.rb +192 -0
  105. data/spec/lib/active_data/model/validations/associated_spec.rb +94 -0
  106. data/spec/lib/active_data/model/validations/nested_spec.rb +93 -0
  107. data/spec/lib/active_data/model/validations_spec.rb +31 -0
  108. data/spec/lib/active_data/model_spec.rb +1 -32
  109. data/spec/lib/active_data_spec.rb +12 -0
  110. data/spec/spec_helper.rb +39 -0
  111. data/spec/support/model_helpers.rb +10 -0
  112. metadata +246 -54
  113. data/gemfiles/Gemfile.rails-3 +0 -14
  114. data/lib/active_data/attributes/base.rb +0 -69
  115. data/lib/active_data/attributes/localized.rb +0 -42
  116. data/lib/active_data/model/associations/association.rb +0 -30
  117. data/lib/active_data/model/attributable.rb +0 -122
  118. data/lib/active_data/model/collectionizable.rb +0 -55
  119. data/lib/active_data/model/collectionizable/proxy.rb +0 -42
  120. data/lib/active_data/model/extensions.rb +0 -9
  121. data/lib/active_data/model/extensions/array.rb +0 -24
  122. data/lib/active_data/model/extensions/big_decimal.rb +0 -17
  123. data/lib/active_data/model/extensions/boolean.rb +0 -38
  124. data/lib/active_data/model/extensions/date.rb +0 -17
  125. data/lib/active_data/model/extensions/date_time.rb +0 -17
  126. data/lib/active_data/model/extensions/float.rb +0 -17
  127. data/lib/active_data/model/extensions/hash.rb +0 -22
  128. data/lib/active_data/model/extensions/integer.rb +0 -17
  129. data/lib/active_data/model/extensions/localized.rb +0 -22
  130. data/lib/active_data/model/extensions/object.rb +0 -17
  131. data/lib/active_data/model/extensions/string.rb +0 -17
  132. data/lib/active_data/model/extensions/time.rb +0 -17
  133. data/lib/active_data/model/localizable.rb +0 -31
  134. data/lib/active_data/model/nested_attributes.rb +0 -58
  135. data/lib/active_data/model/parameterizable.rb +0 -29
  136. data/lib/active_data/validations.rb +0 -7
  137. data/lib/active_data/validations/associated.rb +0 -17
  138. data/spec/lib/active_data/model/attributable_spec.rb +0 -191
  139. data/spec/lib/active_data/model/collectionizable_spec.rb +0 -60
  140. data/spec/lib/active_data/model/nested_attributes_spec.rb +0 -67
  141. data/spec/lib/active_data/model/type_cast_spec.rb +0 -116
  142. data/spec/lib/active_data/validations/associated_spec.rb +0 -88
@@ -0,0 +1,64 @@
1
+ module ActiveData
2
+ module Model
3
+ module Attributes
4
+ class Represents < Attribute
5
+ delegate :reader, :reader_before_type_cast, :writer, to: :reflection
6
+
7
+ def write value
8
+ return if readonly?
9
+ pollute do
10
+ reset
11
+ reference.send(writer, value)
12
+ end
13
+ end
14
+
15
+ def reset
16
+ super
17
+ remove_variable(:cached_value, :cached_value_before_type_cast)
18
+ end
19
+
20
+ def read
21
+ reset if cached_value != read_value
22
+ variable_cache(:value) do
23
+ normalize(enumerize(defaultize(cached_value, read_before_type_cast)))
24
+ end
25
+ end
26
+
27
+ def read_before_type_cast
28
+ reset if cached_value_before_type_cast != read_value_before_type_cast
29
+ variable_cache(:value_before_type_cast) do
30
+ defaultize(cached_value_before_type_cast)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def reference
37
+ owner.send(reflection.reference)
38
+ end
39
+
40
+ def read_value
41
+ ref = reference
42
+ ref.public_send(reader) if ref
43
+ end
44
+
45
+ def cached_value
46
+ variable_cache(:cached_value) { read_value }
47
+ end
48
+
49
+ def read_value_before_type_cast
50
+ ref = reference
51
+ if ref
52
+ ref.respond_to?(reader_before_type_cast) ?
53
+ ref.public_send(reader_before_type_cast) :
54
+ ref.public_send(reader)
55
+ end
56
+ end
57
+
58
+ def cached_value_before_type_cast
59
+ variable_cache(:cached_value_before_type_cast) { read_value_before_type_cast }
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,71 @@
1
+ module ActiveData
2
+ module Model
3
+
4
+ # == Callbacks for ActiveData::Model lifecycle
5
+ #
6
+ # Provides ActiveModel callbacks support for lifecycle
7
+ # actions.
8
+ #
9
+ # class Book
10
+ # include ActiveData::Model
11
+ #
12
+ # attribute :id, Integer
13
+ # attribute :title, String
14
+ #
15
+ # define_save do
16
+ # REDIS.set(id, attributes.to_json)
17
+ # end
18
+ #
19
+ # define_destroy do
20
+ # REDIS.del(instance.id)
21
+ # end
22
+ #
23
+ # after_initialize :setup_id
24
+ # before_save :do_something
25
+ # around_update do |&block|
26
+ # ...
27
+ # block.call
28
+ # ...
29
+ # end
30
+ # after_destroy { ... }
31
+ # end
32
+ #
33
+ module Callbacks
34
+ extend ActiveSupport::Concern
35
+
36
+ included do
37
+ extend ActiveModel::Callbacks
38
+
39
+ include ActiveModel::Validations::Callbacks
40
+ include Lifecycle
41
+ prepend PrependMethods
42
+
43
+ define_model_callbacks :initialize, only: :after
44
+ define_model_callbacks :save, :create, :update, :destroy
45
+ end
46
+
47
+ module PrependMethods
48
+ def initialize *_
49
+ super(*_)
50
+ run_callbacks :initialize
51
+ end
52
+
53
+ def save_object &block
54
+ run_callbacks(:save) { super(&block) }
55
+ end
56
+
57
+ def create_object &block
58
+ run_callbacks(:create) { super(&block) }
59
+ end
60
+
61
+ def update_object &block
62
+ run_callbacks(:update) { super(&block) }
63
+ end
64
+
65
+ def destroy_object &block
66
+ run_callbacks(:destroy) { super(&block) }
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,35 @@
1
+ module ActiveData
2
+ module Model
3
+ module Conventions
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ delegate :logger, to: ActiveData
8
+ self.include_root_in_json = ActiveData.include_root_in_json
9
+ end
10
+
11
+ def persisted?
12
+ false
13
+ end
14
+
15
+ def new_record?
16
+ !persisted?
17
+ end
18
+ alias_method :new_object?, :new_record?
19
+
20
+ module ClassMethods
21
+ def i18n_scope
22
+ ActiveData.i18n_scope
23
+ end
24
+
25
+ def to_ary
26
+ nil
27
+ end
28
+
29
+ def primary_name
30
+ nil
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,77 @@
1
+ module ActiveData
2
+ module Model
3
+ module Dirty
4
+ extend ActiveSupport::Concern
5
+
6
+ ::Module.class_eval do
7
+ alias_method :unconcerned_append_features, :append_features
8
+ end
9
+
10
+ DIRTY_CLONE = ActiveModel::Dirty.clone
11
+ DIRTY_CLONE.class_eval do
12
+ def self.append_features(base)
13
+ unconcerned_append_features(base)
14
+ end
15
+ def self.included(base)
16
+ end
17
+ end
18
+
19
+ included do
20
+ include DIRTY_CLONE
21
+
22
+ unless method_defined?(:set_attribute_was)
23
+ def set_attribute_was(attr, old_value)
24
+ changed_attributes[attr] = old_value
25
+ end
26
+ private :set_attribute_was
27
+ end
28
+
29
+ unless method_defined?(:clear_changes_information)
30
+ if method_defined?(:reset_changes)
31
+ def clear_changes_information
32
+ reset_changes
33
+ end
34
+ else
35
+ def clear_changes_information
36
+ @previously_changed = nil
37
+ @changed_attributes = nil
38
+ end
39
+ end
40
+ end
41
+
42
+ attribute_names(false).each do |name|
43
+ define_dirty name, generated_attributes_methods
44
+ end
45
+ _attribute_aliases.keys.each do |name|
46
+ define_dirty name, generated_attributes_methods
47
+ end
48
+ end
49
+
50
+ module ClassMethods
51
+ def define_dirty method, target = self
52
+ reflection = reflect_on_attribute(method)
53
+ name = reflection ? reflection.name : method
54
+
55
+ %w[changed? change will_change! was
56
+ previously_changed? previous_change].each do |suffix|
57
+ target.class_eval <<-RUBY, __FILE__, __LINE__ + 1
58
+ def #{method}_#{suffix}
59
+ attribute_#{suffix} '#{name}'
60
+ end
61
+ RUBY
62
+ end
63
+
64
+ target.class_eval <<-RUBY, __FILE__, __LINE__ + 1
65
+ def restore_#{method}!
66
+ restore_attribute! '#{name}'
67
+ end
68
+ RUBY
69
+ end
70
+
71
+ def dirty?
72
+ true
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,307 @@
1
+ module ActiveData
2
+ module Model
3
+
4
+ # == Lifecycle methods for ActiveData::Model
5
+ #
6
+ # Provides methods +save+ and +destroy+ and its bang variants.
7
+ # Also, patches +create+ and +update_attributes+ methods by adding
8
+ # save at the end.
9
+ #
10
+ # You can define save or destroy performers with <tt>define_<action></tt>
11
+ # methods. Create and update performers might be defined instead of
12
+ # save performer:
13
+ #
14
+ # class Book
15
+ # include ActiveData::Model
16
+ # include ActiveData::Model::Lifecycle
17
+ #
18
+ # attribute :id, Integer
19
+ # attribute :title, String
20
+ #
21
+ # define_save do # executes in the instance scope
22
+ # REDIS.set(id, attributes.to_json)
23
+ # end
24
+ #
25
+ # define_destroy do
26
+ # REDIS.del(id)
27
+ # end
28
+ # end
29
+ #
30
+ # class Author
31
+ # include ActiveData::Model
32
+ # include ActiveData::Model::Lifecycle
33
+ #
34
+ # attribute :id, Integer
35
+ # attribute :name, String
36
+ #
37
+ # define_create do # will be called on create only
38
+ # REDIS.sadd('author_ids', id)
39
+ # REDIS.set(id, attributes.to_json)
40
+ # end
41
+ #
42
+ # define_update do # will be called on update only
43
+ # REDIS.set(id, attributes.to_json)
44
+ # end
45
+ # end
46
+ #
47
+ # In case of undefined performer ActiveData::UnsavableObject
48
+ # or ActiveData::UndestroyableObject will be raised respectively.
49
+ #
50
+ # If performers was not defined in model, they cat be passed as
51
+ # blocks to `save`, `update` and `destroy` methods:
52
+ #
53
+ # authos.save { REDIS.set(id, attributes.to_json) }
54
+ # authos.update { REDIS.set(id, attributes.to_json) }
55
+ # authos.destroy { REDIS.del(id) }
56
+ #
57
+ # Save and destroy processes acts almost the save way as
58
+ # ActiveRecord's (with +persisted?+ and +destroyed?+ methods
59
+ # affecting).
60
+ #
61
+ module Lifecycle
62
+ extend ActiveSupport::Concern
63
+
64
+ included do
65
+ include Persistence
66
+
67
+ class_attribute *[:save, :create, :update, :destroy].map { |action| "_#{action}_performer" }
68
+ private *[:save, :create, :update, :destroy].map { |action| "_#{action}_performer=" }
69
+ end
70
+
71
+ module ClassMethods
72
+
73
+ # <tt>define_<action></tt> methods define performers for lifecycle
74
+ # actions. Every action block must return boolean result, which
75
+ # would mean the action success. If action performed unsuccessfully
76
+ # ActiveData::ObjectNotSaved or ActiveData::ObjectNotDestroyed will
77
+ # be raised respectively in case of bang methods using.
78
+ #
79
+ # class Author
80
+ # define_create { true }
81
+ # end
82
+ #
83
+ # Author.new.save # => true
84
+ # Author.new.save! # => true
85
+ #
86
+ # class Author
87
+ # define_create { false }
88
+ # end
89
+ #
90
+ # Author.new.save # => false
91
+ # Author.new.save! # => ActiveData::ObjectNotSaved
92
+ #
93
+ # Also performers blocks are executed in the instance context, but
94
+ # instance also passed as argument
95
+ #
96
+ # define_update do |instance|
97
+ # instance.attributes.to_json
98
+ # end
99
+ #
100
+ # +define_create+ and +define_update+ performers has higher priority
101
+ # than +define_save+.
102
+ #
103
+ # class Author
104
+ # define_update { ... }
105
+ # define_save { ... }
106
+ # end
107
+ #
108
+ # author = Author.create # using define_save performer
109
+ # author.update_attributes(...) # using define_update performer
110
+ #
111
+ [:save, :create, :update, :destroy].each do |action|
112
+ define_method "define_#{action}" do |&block|
113
+ send("_#{action}_performer=", block)
114
+ end
115
+ end
116
+
117
+ # Initializes new instance with attributes passed and calls +save+
118
+ # on it. Returns instance in any case.
119
+ #
120
+ def create *args
121
+ new(*args).tap(&:save)
122
+ end
123
+
124
+ # Initializes new instance with attributes passed and calls +save!+
125
+ # on it. Returns instance in case of success and raises ActiveData::ValidationError
126
+ # or ActiveData::ObjectNotSaved in case of validation or saving fail respectively.
127
+ #
128
+ def create! *args
129
+ new(*args).tap(&:save!)
130
+ end
131
+ end
132
+
133
+ # <tt>define_<action></tt> on instance level works the same
134
+ # way as class <tt>define_<action></tt> methods, but defines
135
+ # performers for instance only
136
+ #
137
+ # user.define_save do
138
+ # REDIS.set(id, attributes.to_json)
139
+ # end
140
+ # user.save! # => will use instance-level performer
141
+ #
142
+ [:save, :create, :update, :destroy].each do |action|
143
+ define_method "define_#{action}" do |&block|
144
+ send("_#{action}_performer=", block)
145
+ end
146
+ end
147
+
148
+ # Assigns passed attributes and calls +save+
149
+ # Returns true or false in case of successful or unsuccessful
150
+ # saving respectively.
151
+ #
152
+ # author.update(name: 'Donald')
153
+ #
154
+ # If update performer is not defined with `define_update`
155
+ # or `define_save`, it raises ActiveData::UnsavableObject.
156
+ # Also save performer block might be passed instead of in-class
157
+ # performer definition:
158
+ #
159
+ # author.update(name: 'Donald') { REDIS.set(id, attributes.to_json) }
160
+ #
161
+ def update attributes, &block
162
+ assign_attributes(attributes) && save(&block)
163
+ end
164
+ alias_method :update_attributes, :update
165
+
166
+ # Assigns passed attributes and calls +save!+
167
+ # Returns true in case of success and raises ActiveData::ValidationError
168
+ # or ActiveData::ObjectNotSaved in case of validation or
169
+ # saving fail respectively.
170
+ #
171
+ # author.update!(name: 'Donald')
172
+ #
173
+ # If update performer is not defined with `define_update`
174
+ # or `define_save`, it raises ActiveData::UnsavableObject.
175
+ # Also save performer block might be passed instead of in-class
176
+ # performer definition:
177
+ #
178
+ # author.update!(name: 'Donald') { REDIS.set(id, attributes.to_json) }
179
+ #
180
+ def update! attributes, &block
181
+ assign_attributes(attributes) && save!(&block)
182
+ end
183
+ alias_method :update_attributes!, :update!
184
+
185
+ # # Saves object by calling save performer defined with +define_save+,
186
+ # +define_create+ or +define_update+ methods.
187
+ # Returns true or false in case of successful
188
+ # or unsuccessful saving respectively. Changes +persisted?+ to true
189
+ #
190
+ # author.save
191
+ #
192
+ # If save performer is not defined with `define_update` or
193
+ # `define_create` or `define_save`, it raises ActiveData::UnsavableObject.
194
+ # Also save performer block might be passed instead of in-class
195
+ # performer definition:
196
+ #
197
+ # author.save { REDIS.set(id, attributes.to_json) }
198
+ #
199
+ def save options = {}, &block
200
+ raise ActiveData::UnsavableObject unless block || savable?
201
+ valid? && save_object(&block)
202
+ end
203
+
204
+ # Saves object by calling save performer defined with +define_save+,
205
+ # +define_create+ or +define_update+ methods.
206
+ # Returns true in case of success and raises ActiveData::ValidationError
207
+ # or ActiveData::ObjectNotSaved in case of validation or
208
+ # saving fail respectively. Changes +persisted?+ to true
209
+ #
210
+ # author.save!
211
+ #
212
+ # If save performer is not defined with `define_update` or
213
+ # `define_create` or `define_save`, it raises ActiveData::UnsavableObject.
214
+ # Also save performer block might be passed instead of in-class
215
+ # performer definition:
216
+ #
217
+ # author.save! { REDIS.set(id, attributes.to_json) }
218
+ #
219
+ def save! options = {}, &block
220
+ raise ActiveData::UnsavableObject unless block || savable?
221
+ validate!
222
+ save_object(&block) or raise ActiveData::ObjectNotSaved
223
+ end
224
+
225
+ # Destroys object by calling the destroy performer.
226
+ # Returns instance in any case. Changes +persisted?+
227
+ # to false and +destroyed?+ to true in case of success.
228
+ #
229
+ # author.destroy
230
+ #
231
+ # If destroy performer is not defined with `define_destroy`,
232
+ # it raises ActiveData::UndestroyableObject.
233
+ # Also destroy performer block might be passed instead of in-class
234
+ # performer definition:
235
+ #
236
+ # author.destroy { REDIS.del(id) }
237
+ #
238
+ def destroy &block
239
+ raise ActiveData::UndestroyableObject unless block || destroyable?
240
+ destroy_object(&block)
241
+ self
242
+ end
243
+
244
+ # Destroys object by calling the destroy performer.
245
+ # In case of success returns instance and changes +persisted?+
246
+ # to false and +destroyed?+ to true.
247
+ # Raises ActiveData::ObjectNotDestroyed in case of fail.
248
+ #
249
+ # author.destroy!
250
+ #
251
+ # If destroy performer is not defined with `define_destroy`,
252
+ # it raises ActiveData::UndestroyableObject.
253
+ # Also destroy performer block might be passed instead of in-class
254
+ # performer definition:
255
+ #
256
+ # author.destroy! { REDIS.del(id) }
257
+ #
258
+ def destroy! &block
259
+ raise ActiveData::UndestroyableObject unless block || destroyable?
260
+ destroy_object(&block) or raise ActiveData::ObjectNotDestroyed
261
+ self
262
+ end
263
+
264
+ private
265
+
266
+ def savable?
267
+ !!((persisted? ? _update_performer : _create_performer) || _save_performer)
268
+ end
269
+
270
+ def save_object &block
271
+ apply_association_changes! if respond_to?(:apply_association_changes!)
272
+ result = persisted? ? update_object(&block) : create_object(&block)
273
+ mark_persisted! if result
274
+ result
275
+ end
276
+
277
+ def create_object &block
278
+ performer = block || _create_performer || _save_performer
279
+ !!performer_exec(&performer)
280
+ end
281
+
282
+ def update_object &block
283
+ performer = block || _update_performer || _save_performer
284
+ !!performer_exec(&performer)
285
+ end
286
+
287
+ def destroyable?
288
+ !!_destroy_performer
289
+ end
290
+
291
+ def destroy_object &block
292
+ performer = block || _destroy_performer
293
+ result = !!performer_exec(&performer)
294
+ mark_destroyed! if result
295
+ result
296
+ end
297
+
298
+ def performer_exec &block
299
+ if block.arity == 1
300
+ block.call(self)
301
+ else
302
+ instance_exec(&block)
303
+ end
304
+ end
305
+ end
306
+ end
307
+ end