object_attorney 0.0.1

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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in object_attorney.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 goncalvesjoao
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # ObjectAttorney
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'object_attorney'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install object_attorney
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,3 @@
1
+ module ObjectAttorney
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,307 @@
1
+ require "object_attorney/version"
2
+
3
+ module ObjectAttorney
4
+
5
+ ################# EXAMPLES ##################
6
+ # represents :user
7
+ # accepts_nested_objects :addess, :posts
8
+ #############################################
9
+
10
+ def initialize(attributes = {}, object = nil, options = {})
11
+ if !attributes.kind_of?(Hash) && object.blank?
12
+ object = attributes
13
+ attributes = nil
14
+ end
15
+
16
+ attributes = {} if attributes.blank?
17
+
18
+ if !attributes.include?("id") && object.kind_of?(String)
19
+ attributes["id"] = object
20
+ object = nil
21
+ end
22
+
23
+ @object = object if object.present?
24
+ @trusted_data = options[:trusted_data] if options.present? && options.kind_of?(Hash)
25
+
26
+ assign_attributes attributes
27
+ mark_for_destruction_if_necessary(self, attributes)
28
+ end
29
+
30
+ def nested_objects
31
+ unless nested_objects_updated
32
+ @nested_objects = self.class.instance_variable_get("@nested_objects").map { |nested_object_sym| self.send(nested_object_sym) }.flatten
33
+ nested_objects_updated = true
34
+ end
35
+
36
+ @nested_objects
37
+ end
38
+
39
+ def new_record?
40
+ @object.try_or_return(:new_record?, true)
41
+ end
42
+
43
+ def persisted?
44
+ @object.try_or_return(:persisted?, false)
45
+ end
46
+
47
+ def mark_for_destruction
48
+ @marked_for_destruction = true
49
+ end
50
+
51
+ def marked_for_destruction?
52
+ @marked_for_destruction
53
+ end
54
+
55
+ def save
56
+ save_process
57
+ end
58
+
59
+ def save!
60
+ save_process true
61
+ end
62
+
63
+ def destroy
64
+ @object.try_or_return(:destroy, true) && nested_objects.all?(&:destroy)
65
+ end
66
+
67
+ def assign_attributes(attributes = {})
68
+ return if attributes.blank?
69
+
70
+ attributes.each do |name, value|
71
+ send("#{name}=", value) if allowed_attribute(name)
72
+ end
73
+ end
74
+
75
+ def mark_for_destruction_if_necessary(object, attributes)
76
+ return nil unless attributes.kind_of?(Hash)
77
+
78
+ _destroy = attributes["_destroy"] || attributes[:_destroy]
79
+
80
+ object.mark_for_destruction if ["true", "1"].include?(_destroy)
81
+ end
82
+
83
+ def read_attribute_for_serialization(attribute)
84
+ respond_to?(attribute) ? send(attribute) : nil
85
+ end
86
+
87
+ protected #--------------------------------------------------protected
88
+
89
+ def self.included(base)
90
+ base.extend(ClassMethods)
91
+
92
+ base.class_eval do
93
+ include ActiveModel::Validations
94
+ include ActiveModel::Conversion
95
+
96
+ attr_accessor :nested_objects_updated, :trusted_data
97
+ validate :validate_own_object, :validate_nested_objects
98
+
99
+ def valid?
100
+ override_valid? ? true : super
101
+ end
102
+ end
103
+
104
+ base.instance_variable_set("@nested_objects", [])
105
+ base.instance_variable_set("@white_list", [])
106
+ base.instance_variable_set("@black_list", [])
107
+ end
108
+
109
+ def allowed_attribute(attribute)
110
+ attribute = attribute.to_s
111
+
112
+ return false if !respond_to?("#{attribute}=") || black_list.include?(attribute)
113
+ return true if self.class.instance_variable_get("@white_list").empty?
114
+
115
+ self.class.instance_variable_get("@white_list").include?(attribute)
116
+ end
117
+
118
+ def black_list
119
+ [*self.class.instance_variable_get("@black_list"), "trusted_data", "nested_objects_updated", "_destroy"]
120
+ end
121
+
122
+ def save_process(raise_exception = false)
123
+ before_save
124
+ save_result = raise_exception ? _save! : _save
125
+ after_save if save_result
126
+ save_result
127
+ end
128
+
129
+ def before_save; end
130
+ def after_save; end
131
+
132
+ def _save
133
+ begin
134
+ ActiveRecord::Base.transaction { _save! }
135
+ rescue
136
+ valid?
137
+ false
138
+ end
139
+ end
140
+
141
+ def _save!
142
+ result = (save_or_raise_rollback! ? save_or_destroy_nested_objects : false)
143
+ valid?
144
+ result
145
+ end
146
+
147
+ def save_or_raise_rollback!
148
+ if valid?
149
+ save_own_object
150
+ else
151
+ raise ActiveRecord::Rollback
152
+ end
153
+ end
154
+
155
+ def save_own_object
156
+ @object.try_or_return(:save!, true)
157
+ end
158
+
159
+ def save_or_destroy_nested_objects
160
+ result = nested_objects.map do |nested_object|
161
+ nested_object.marked_for_destruction? ? nested_object.destroy : nested_object.save!
162
+ end.all?
163
+
164
+ self.errors.add(:base, "Some errors where found while saving the nested objects.") unless result
165
+
166
+ result
167
+ end
168
+
169
+ def validate_own_object
170
+ valid = override_valid? ? true : @object.try_or_return(:valid?, true)
171
+ import_own_object_errors unless valid
172
+ valid
173
+ end
174
+
175
+ def validate_nested_objects
176
+ #nested_objects.all?(&:valid?) #will not validate all nested_objects
177
+ return true if nested_objects.reject(&:marked_for_destruction?).map(&:valid?).all?
178
+ import_nested_objects_errors
179
+ false
180
+ end
181
+
182
+ def get_attributes_for_existing(nested_object_name, existing_nested_object)
183
+ attributes = send("#{nested_object_name}_attributes")
184
+ return {} if attributes.blank?
185
+ attributes.present? ? (attributes.values.select { |x| x[:id].to_i == existing_nested_object.id }.first || {}) : {}
186
+ end
187
+
188
+ def import_own_object_errors
189
+ @object.errors.each { |key, value| self.errors.add(key, value) }
190
+ end
191
+
192
+ def import_nested_objects_errors
193
+ self.class.instance_variable_get("@nested_objects").map do |nested_object_sym|
194
+
195
+ [*self.send(nested_object_sym)].each do |nested_object|
196
+ nested_object.errors.full_messages.each { |message| self.errors.add(nested_object_sym, message) }
197
+ end
198
+
199
+ end
200
+ end
201
+
202
+ # def return_hash_with_existing_errors(hash, object = nil)
203
+ # object ||= self
204
+ # object.errors.empty? ? hash : hash.merge({ errors: object.errors })
205
+ # end
206
+
207
+ private #------------------------------ private
208
+
209
+ def attributes_without_destroy(attributes)
210
+ return nil unless attributes.kind_of?(Hash)
211
+
212
+ _attributes = attributes.dup
213
+ _attributes.delete("_destroy")
214
+ _attributes.delete(:_destroy)
215
+
216
+ _attributes
217
+ end
218
+
219
+ def override_valid?
220
+ marked_for_destruction?
221
+ end
222
+
223
+ def nested_getter(nested_object_name)
224
+ nested_instance_variable = self.instance_variable_get("@#{nested_object_name}")
225
+
226
+ if nested_instance_variable.nil?
227
+ nested_instance_variable = get_existing_and_new_nested_objects(nested_object_name)
228
+ self.instance_variable_set("@#{nested_object_name}", nested_instance_variable)
229
+ end
230
+
231
+ nested_instance_variable
232
+ end
233
+
234
+ def get_existing_and_new_nested_objects(nested_object_name)
235
+ existing_and_new_nested_objects = []
236
+
237
+ update_existing_nested_objects(existing_and_new_nested_objects, nested_object_name)
238
+ build_new_nested_objects(existing_and_new_nested_objects, nested_object_name)
239
+
240
+ existing_and_new_nested_objects
241
+ end
242
+
243
+ def update_existing_nested_objects(existing_and_new_nested_objects, nested_object_name)
244
+ (send("existing_#{nested_object_name}") || []).each do |existing_nested_object|
245
+ attributes = get_attributes_for_existing(nested_object_name, existing_nested_object)
246
+
247
+ mark_for_destruction_if_necessary(existing_nested_object, attributes)
248
+ existing_nested_object.assign_attributes(attributes_without_destroy(attributes))
249
+
250
+ existing_and_new_nested_objects << existing_nested_object
251
+ end
252
+ end
253
+
254
+ def build_new_nested_objects(existing_and_new_nested_objects, nested_object_name)
255
+ (send("#{nested_object_name}_attributes") || {}).values.each do |attributes|
256
+ next if attributes["id"].present?
257
+
258
+ new_nested_object = send("build_#{nested_object_name.to_s.singularize}", attributes_without_destroy(attributes))
259
+ mark_for_destruction_if_necessary(new_nested_object, attributes)
260
+
261
+ existing_and_new_nested_objects << new_nested_object
262
+ end
263
+ end
264
+
265
+ module ClassMethods
266
+
267
+ attr_accessor :own_object_class
268
+
269
+ def represents(own_object)
270
+ define_method(own_object) do
271
+ own_object_class = self.class.instance_variable_get(:@own_object_class)
272
+ own_object_class ||= own_object.to_s.camelize
273
+ @object ||= own_object_class.constantize.new
274
+ end
275
+ end
276
+
277
+ def accepts_nested_objects(*nested_objects_list)
278
+ self.instance_variable_set("@nested_objects", nested_objects_list)
279
+ self.send(:attr_accessor, *nested_objects_list.map { |attribute| "#{attribute}_attributes".to_sym })
280
+ define_nested_objects_getter_methods nested_objects_list
281
+ end
282
+
283
+ def attr_white_list(*white_list)
284
+ self.class.instance_variable_set("@white_list", white_list.map(&:to_s))
285
+ end
286
+
287
+ def attr_black_list(*black_list)
288
+ self.class.instance_variable_set("@black_list", black_list.map(&:to_s))
289
+ end
290
+
291
+ def reflect_on_association(association)
292
+ nil
293
+ end
294
+
295
+ private #------------------------ private
296
+
297
+ def define_nested_objects_getter_methods(nested_objects_list)
298
+ nested_objects_list.each do |nested_object_name|
299
+ define_method(nested_object_name) do
300
+ nested_getter(nested_object_name)
301
+ end
302
+ end
303
+ end
304
+
305
+ end
306
+
307
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'object_attorney/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "object_attorney"
8
+ spec.version = ObjectAttorney::VERSION
9
+ spec.authors = ["João Gonçalves"]
10
+ spec.email = ["goncalves.joao@gmail.com"]
11
+ spec.description = %q{Form Object Patter Implementation}
12
+ spec.summary = %q{This gem allows you to extract the code responsible for Validations, Nested Objects and Forms, from your model, into a specific class for a specific use case.}
13
+ spec.homepage = "https://github.com/goncalvesjoao/object_attorney"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+
24
+ spec.add_dependency(%q<rails>, [">= 3.0.0"])
25
+ spec.add_dependency(%q<actionpack>, [">= 3.0.0"])
26
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: object_attorney
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - João Gonçalves
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-02 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rails
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: 3.0.0
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 3.0.0
62
+ - !ruby/object:Gem::Dependency
63
+ name: actionpack
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: 3.0.0
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: 3.0.0
78
+ description: Form Object Patter Implementation
79
+ email:
80
+ - goncalves.joao@gmail.com
81
+ executables: []
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - .gitignore
86
+ - Gemfile
87
+ - LICENSE.txt
88
+ - README.md
89
+ - Rakefile
90
+ - lib/object_attorney.rb
91
+ - lib/object_attorney/version.rb
92
+ - object_attorney.gemspec
93
+ homepage: https://github.com/goncalvesjoao/object_attorney
94
+ licenses:
95
+ - MIT
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ! '>='
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubyforge_project:
114
+ rubygems_version: 1.8.23
115
+ signing_key:
116
+ specification_version: 3
117
+ summary: This gem allows you to extract the code responsible for Validations, Nested
118
+ Objects and Forms, from your model, into a specific class for a specific use case.
119
+ test_files: []
120
+ has_rdoc: