object_attorney 1.2.1 → 2.1.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.
Files changed (44) hide show
  1. checksums.yaml +8 -8
  2. data/db/migrate/20131205114000_create_users.rb +13 -0
  3. data/db/migrate/20131205114900_create_posts.rb +1 -1
  4. data/db/migrate/20131205114901_create_comments.rb +13 -0
  5. data/db/migrate/20131205114902_create_addresses.rb +13 -0
  6. data/lib/object_attorney/association_reflection.rb +52 -15
  7. data/lib/object_attorney/helpers.rb +4 -0
  8. data/lib/object_attorney/nested_objects.rb +88 -21
  9. data/lib/object_attorney/orm.rb +20 -10
  10. data/lib/object_attorney/orm_handlers/smooth_operator.rb +20 -15
  11. data/lib/object_attorney/reflection.rb +35 -0
  12. data/lib/object_attorney/version.rb +1 -1
  13. data/lib/object_attorney.rb +18 -10
  14. data/spec/object_attorney/bulk_post_form_spec.rb +52 -0
  15. data/spec/object_attorney/bulk_posts_allow_only_existing_form_spec.rb +37 -0
  16. data/spec/object_attorney/bulk_posts_allow_only_new_form_spec.rb +39 -0
  17. data/spec/object_attorney/bulk_posts_with_form_objects_form_spec.rb +91 -0
  18. data/spec/object_attorney/post_form_spec.rb +107 -41
  19. data/spec/object_attorney/post_with_comment_form_spec.rb +123 -0
  20. data/spec/object_attorney/post_with_comments_and_address_form_spec.rb +45 -0
  21. data/spec/object_attorney/user_form_spec.rb +61 -0
  22. data/spec/spec_helper.rb +15 -6
  23. data/spec/support/form_objects/bulk_posts_allow_only_existing_form.rb +19 -0
  24. data/spec/support/form_objects/bulk_posts_allow_only_new_form.rb +19 -0
  25. data/spec/support/form_objects/bulk_posts_form.rb +27 -0
  26. data/spec/support/form_objects/bulk_posts_with_form_objects_form.rb +27 -0
  27. data/spec/support/form_objects/comment_form.rb +11 -0
  28. data/spec/support/form_objects/post_form.rb +54 -0
  29. data/spec/support/form_objects/post_with_comment_form.rb +21 -0
  30. data/spec/support/form_objects/post_with_comments_and_address_form.rb +13 -0
  31. data/spec/support/form_objects/user_form.rb +11 -0
  32. data/spec/support/models/address.rb +5 -0
  33. data/spec/support/models/comment.rb +5 -0
  34. data/spec/support/models/post.rb +7 -1
  35. data/spec/support/models/user.rb +7 -0
  36. metadata +44 -19
  37. data/spec/object_attorney/bulk_posts_form_child_spec.rb +0 -191
  38. data/spec/object_attorney/bulk_posts_form_spec.rb +0 -178
  39. data/spec/object_attorney/post_form_child_spec.rb +0 -60
  40. data/spec/support/models/bulk_posts_form.rb +0 -21
  41. data/spec/support/models/bulk_posts_form_child.rb +0 -19
  42. data/spec/support/models/item.rb +0 -3
  43. data/spec/support/models/post_form.rb +0 -13
  44. data/spec/support/models/post_form_child.rb +0 -7
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZDBmMmQ5OTNkMjI2MzRkZGVkNjM5NzgwMDg1NzQ0ZjYzNjhiODUwMQ==
4
+ ZGE1NWUyMGQ1NzE4YjIyMmM0ZmJhN2YxYWJiZjczYmM1MTk5YzA1Mw==
5
5
  data.tar.gz: !binary |-
6
- YmQxOGY0OGNiMzEyNDQzOTcxOGM3MGRmZjhiOTVlYTdiOTdlNGVmMw==
6
+ OTU5Y2FkZThiNDQ2Y2ZjZjhkYmIyNzhlN2ZlNmJhOWQ3OGZmOWU4ZA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZTU2YjYyYzQ4M2JkZDJjZTZlZTg1N2JjMjNmM2JkZjhmMzczNWRiZjQ1MTBk
10
- YzZkYzBiNDg2ZjlhMTU5ZGNhZDIzNWJmNDI4MTQzZWFiYWFjYmQzZjBhZWRl
11
- ZjZlYWY1MDk4ZmVlZThkMGE1YTE5OTg1ODVlOTZmMDJjNjIxNjc=
9
+ OTliNTEyNjU2ZjRhM2QwNjEyOTMyNmFiYzM4ZDJmYTViMmYwMzM0NDE4Y2Fj
10
+ ZGIzMDhhMjk1YjdjZjFjNWExYzU0NTRiZTdmOGM0NDJmZDk5OWExN2M2MWJk
11
+ MTI2MTdhNTIyMmExYTQ1MjVlYjY5NmVlZmI4ZGFmZTEwMzE3MDI=
12
12
  data.tar.gz: !binary |-
13
- NzY5MmJjMTMxMWU0NzVlYzZlZDYwNzM3YzNkM2Y0MzY2ZWIxYWU3MTUwNWYx
14
- NWUxNzE3NWUzNWMzZjc4YjI5YjQ3YTM1Y2Q1YWQ2YWZjZDllOGVjM2YzODMw
15
- YzRkYTA0ODI4NTMwZjRkZTU5MWM3MDJkYWQxYWNlYzA0ZmFiOTY=
13
+ MDdiMmRjMjgxMDdkYmNiODU1MTMxYTA0ODU0M2JjZDVmMDYyYjNhNGFlMzFm
14
+ NDZmODY0NWUxNGYxN2Q2ZTA0NTY3YjBjODMxMTc4Y2IxM2UyNzViZWMyMjA1
15
+ ZGVjNzMyOWEzZGMyNjIzYjA1MzY0NzkzMWJmMGEzODRiMzE0OTU=
@@ -0,0 +1,13 @@
1
+ class CreateUsers < ActiveRecord::Migration
2
+ def up
3
+ create_table :users do |t|
4
+ t.string :email
5
+ t.boolean :admin, default: false
6
+ t.timestamps
7
+ end
8
+ end
9
+
10
+ def down
11
+ drop_table :users
12
+ end
13
+ end
@@ -3,7 +3,7 @@ class CreatePosts < ActiveRecord::Migration
3
3
  create_table :posts do |t|
4
4
  t.string :title
5
5
  t.text :body
6
- t.boolean :admin, default: false
6
+ t.integer :user_id
7
7
  t.timestamps
8
8
  end
9
9
  end
@@ -0,0 +1,13 @@
1
+ class CreateComments < ActiveRecord::Migration
2
+ def up
3
+ create_table :comments do |t|
4
+ t.text :body
5
+ t.integer :post_id
6
+ t.timestamps
7
+ end
8
+ end
9
+
10
+ def down
11
+ drop_table :comments
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ class CreateAddresses < ActiveRecord::Migration
2
+ def up
3
+ create_table :addresses do |t|
4
+ t.string :street
5
+ t.integer :post_id
6
+ t.timestamps
7
+ end
8
+ end
9
+
10
+ def down
11
+ drop_table :addresses
12
+ end
13
+ end
@@ -1,17 +1,54 @@
1
1
  module ObjectAttorney
2
2
 
3
- class AssociationReflection
4
- attr_reader :klass, :macro, :options, :name
3
+ class AssociationReflection < Reflection
4
+ attr_reader :related_reflection, :macro
5
5
 
6
- def initialize(association, options)
7
- options = options.is_a?(Hash) ? options : { class_name: options }
8
-
9
- @name = association
6
+ def initialize(association, related_reflection, options)
7
+ super(association, options)
10
8
  @macro = options[:macro] || macro_default(association)
11
- @klass = options[:class_name] || klass_default(association)
12
- @options = options
9
+ @related_reflection = related_reflection
10
+ end
11
+
12
+ def primary_key
13
+ @primary_key ||= options[:primary_key] || :id
14
+ end
15
+
16
+ def foreign_key
17
+ @foreign_key ||= options[:foreign_key] || foreign_key_default
18
+ end
19
+
20
+ def set_relational_keys(origin, destination)
21
+ if has_many? || has_one?
22
+ set_foreign_key(destination, primary_key_of(origin))
23
+ elsif belongs_to?
24
+ set_foreign_key(origin, primary_key_of(destination))
25
+ end
26
+ end
27
+
28
+ def set_foreign_key(object, id)
29
+ setter = "#{foreign_key}="
30
+
31
+ if object.respond_to?(setter)
32
+ object.send(setter, id)
33
+ elsif object.respond_to?("send_to_representative")
34
+ object.send_to_representative(setter, id)
35
+ end
36
+ end
37
+
38
+ def primary_key_of(object)
39
+ object.send(primary_key)
40
+ end
13
41
 
14
- @klass = @klass.constantize if @klass.is_a?(String)
42
+ def has_many?
43
+ macro == :has_many
44
+ end
45
+
46
+ def has_one?
47
+ macro == :has_one
48
+ end
49
+
50
+ def belongs_to?
51
+ macro == :belongs_to
15
52
  end
16
53
 
17
54
  private ################################# private
@@ -20,12 +57,12 @@ module ObjectAttorney
20
57
  Helpers.plural?(association) ? :has_many : :belongs_to
21
58
  end
22
59
 
23
- def klass_default(association)
24
- if Helpers.plural?(association)
25
- association.to_s.singularize.camelize
26
- else
27
- association.to_s.camelize
28
- end
60
+ def foreign_key_default
61
+ if has_many? || has_one?
62
+ "#{related_reflection.single_name}_id"
63
+ elsif belongs_to?
64
+ "#{single_name}_id"
65
+ end.to_sym
29
66
  end
30
67
 
31
68
  end
@@ -4,6 +4,10 @@ module ObjectAttorney
4
4
 
5
5
  extend self
6
6
 
7
+ def is_integer?(string)
8
+ string.match(/^(\d)+$/)
9
+ end
10
+
7
11
  def plural?(string)
8
12
  string = string.to_s
9
13
  string == string.pluralize
@@ -1,10 +1,8 @@
1
+ require "object_attorney/association_reflection"
2
+
1
3
  module ObjectAttorney
2
4
  module NestedObjects
3
5
 
4
- def nested_objects
5
- self.class.nested_objects.map { |nested_object_sym| self.send(nested_object_sym) }.flatten
6
- end
7
-
8
6
  def mark_for_destruction
9
7
  @marked_for_destruction = true
10
8
  end
@@ -14,35 +12,54 @@ module ObjectAttorney
14
12
  end
15
13
 
16
14
  def mark_for_destruction_if_necessary(object, attributes)
17
- return nil unless attributes.kind_of?(Hash)
15
+ return nil unless attributes.is_a?(Hash)
18
16
 
19
17
  _destroy = attributes["_destroy"] || attributes[:_destroy]
20
18
 
21
19
  object.mark_for_destruction if ["true", "1", true].include?(_destroy)
22
20
  end
23
21
 
22
+ def nested_objects(macro = nil)
23
+ nested_objects_list = []
24
+
25
+ self.class.reflect_on_all_associations(macro).each do |reflection|
26
+ [*self.send(reflection.name)].each do |nested_object|
27
+ nested_objects_list << [reflection, nested_object]
28
+ end
29
+ end
30
+
31
+ nested_objects_list
32
+ end
33
+
24
34
  protected #################### PROTECTED METHODS DOWN BELOW ######################
25
35
 
26
- def save_nested_objects(save_method)
27
- nested_objects.map do |nested_object|
28
- call_save_or_destroy(nested_object, save_method)
36
+ def save_or_destroy_nested_objects(save_method, association_macro)
37
+ nested_objects(association_macro).map do |reflection, nested_object|
38
+
39
+ populate_foreign_key(self, nested_object, reflection, :has_many)
40
+ populate_foreign_key(self, nested_object, reflection, :has_one)
41
+
42
+ saving_result = call_save_or_destroy(nested_object, save_method)
43
+
44
+ populate_foreign_key(self, nested_object, reflection, :belongs_to)
45
+
46
+ saving_result
47
+
29
48
  end.all?
30
49
  end
31
50
 
32
51
  def validate_nested_objects
33
- #nested_objects.all?(&:valid?) #will not validate all nested_objects
34
- return true if nested_objects.reject(&:marked_for_destruction?).map(&:valid?).all?
52
+ return true if nested_objects.map do |reflection, nested_object|
53
+ nested_object.marked_for_destruction? ? true : nested_object.valid?
54
+ end.all?
55
+
35
56
  import_nested_objects_errors
36
57
  false
37
58
  end
38
59
 
39
60
  def import_nested_objects_errors
40
- self.class.nested_objects.map do |nested_object_sym|
41
-
42
- [*self.send(nested_object_sym)].each do |nested_object|
43
- nested_object.errors.full_messages.each { |message| self.errors.add(nested_object_sym, message) }
44
- end
45
-
61
+ nested_objects.each do |reflection, nested_object|
62
+ nested_object.errors.full_messages.each { |message| self.errors.add(reflection.name, message) }
46
63
  end
47
64
  end
48
65
 
@@ -54,6 +71,12 @@ module ObjectAttorney
54
71
 
55
72
  private #################### PRIVATE METHODS DOWN BELOW ######################
56
73
 
74
+ def populate_foreign_key(origin, destination, reflection, macro)
75
+ return nil if represented_object.blank? || check_if_marked_for_destruction?(destination) || reflection.macro != macro
76
+
77
+ reflection.set_relational_keys(origin, destination)
78
+ end
79
+
57
80
  def self.included(base)
58
81
  base.extend(ClassMethods)
59
82
 
@@ -93,7 +116,7 @@ module ObjectAttorney
93
116
  end
94
117
 
95
118
  def update_existing_nested_objects(existing_and_new_nested_objects, nested_object_name)
96
- (send("existing_#{nested_object_name}") || []).each do |existing_nested_object|
119
+ send("existing_#{nested_object_name}").each do |existing_nested_object|
97
120
  attributes = get_attributes_for_existing(nested_object_name, existing_nested_object)
98
121
 
99
122
  mark_for_destruction_if_necessary(existing_nested_object, attributes)
@@ -108,21 +131,60 @@ module ObjectAttorney
108
131
  next if attributes["id"].present? || attributes[:id].present?
109
132
 
110
133
  new_nested_object = send("build_#{nested_object_name.to_s.singularize}", attributes_without_destroy(attributes))
111
- mark_for_destruction_if_necessary(new_nested_object, attributes)
134
+ next unless new_nested_object
112
135
 
136
+ mark_for_destruction_if_necessary(new_nested_object, attributes)
113
137
  existing_and_new_nested_objects << new_nested_object
114
138
  end
115
139
  end
116
140
 
141
+ def build_nested_object(nested_object_name, attributes = {})
142
+ reflection = self.class.reflect_on_association(nested_object_name)
143
+
144
+ new_nested_object = reflection.klass.new(attributes)
145
+
146
+ populate_foreign_key(self, new_nested_object, reflection, :has_many)
147
+
148
+ new_nested_object
149
+ end
150
+
151
+ def existing_nested_objects(nested_object_name)
152
+ nested_association_klass = self.class.reflect_on_association(nested_object_name).klass
153
+
154
+ existing_list = represented_object.blank? ? nested_association_klass.all : (represented_object.send(nested_object_name) || [])
155
+
156
+ if represented_object.present? && nested_association_klass != self.class.represented_object_class.reflect_on_association(nested_object_name).klass
157
+ existing_list = existing_list.map { |existing_nested_object| nested_association_klass.new({}, existing_nested_object) }
158
+ end
159
+
160
+ existing_list
161
+ end
162
+
117
163
  module ClassMethods
118
164
 
165
+ def has_many(nested_object_name, options = {})
166
+ _accepts_nested_objects_overwrite_macro(nested_object_name, options, :has_many)
167
+ end
168
+
169
+ def has_one(nested_object_name, options = {})
170
+ _accepts_nested_objects_overwrite_macro(nested_object_name, options, :has_one)
171
+ end
172
+
173
+ def belongs_to(nested_object_name, options = {})
174
+ _accepts_nested_objects_overwrite_macro(nested_object_name, options, :belongs_to)
175
+ end
176
+
119
177
  def accepts_nested_objects(nested_object_name, options = {})
120
- reflection = AssociationReflection.new(nested_object_name, options)
178
+ reflection = AssociationReflection.new(nested_object_name, represented_object_reflection, options)
179
+
121
180
  self.instance_variable_set("@#{nested_object_name}_reflection", reflection)
122
181
  self.instance_variable_set("@association_reflections", association_reflections | [reflection])
123
182
 
124
183
  self.send(:attr_accessor, "#{nested_object_name}_attributes".to_sym)
184
+
125
185
  define_method(nested_object_name) { nested_getter(nested_object_name) }
186
+ define_method("build_#{reflection.single_name}") { |attributes = {}, nested_object = nil| build_nested_object(nested_object_name, attributes) }
187
+ define_method("existing_#{nested_object_name}") { existing_nested_objects(nested_object_name) }
126
188
  end
127
189
 
128
190
  def association_reflections
@@ -137,8 +199,13 @@ module ObjectAttorney
137
199
  macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
138
200
  end
139
201
 
140
- def nested_objects
141
- association_reflections.map(&:name)
202
+
203
+ private ############################### PRIVATE METHODS ###########################
204
+
205
+ def _accepts_nested_objects_overwrite_macro(nested_object_name, options, macro)
206
+ default_options = { macro: macro }
207
+ options = options.is_a?(Hash) ? options.merge(default_options) : default_options
208
+ accepts_nested_objects(nested_object_name, options)
142
209
  end
143
210
 
144
211
  end
@@ -26,15 +26,15 @@ module ObjectAttorney
26
26
 
27
27
  def destroy
28
28
  return true if represented_object.blank?
29
- evoke_method_on_object(represented_object, :destroy)
29
+ represented_object.destroy
30
30
  end
31
31
 
32
32
  def call_save_or_destroy(object, save_method)
33
33
  if object == self
34
- represented_object.present? ? evoke_method_on_object(represented_object, save_method) : true
34
+ represented_object.present? ? represented_object.send(save_method) : true
35
35
  else
36
36
  save_method = :destroy if check_if_marked_for_destruction?(object)
37
- evoke_method_on_object(object, save_method)
37
+ object.send(save_method)
38
38
  end
39
39
  end
40
40
 
@@ -48,15 +48,29 @@ module ObjectAttorney
48
48
  end
49
49
 
50
50
  def submit(save_method)
51
- save_result = save_represented_object(save_method)
52
- save_result = save_nested_objects(save_method) if save_result
51
+ save_result = save_or_destroy_nested_objects(save_method, :belongs_to)
52
+ save_result = save_or_destroy_represented_object(save_method) if save_result
53
+ save_result = save_or_destroy_nested_objects(save_method, :has_many) if save_result
54
+ save_result = save_or_destroy_nested_objects(save_method, :has_one) if save_result
53
55
  save_result
54
56
  end
55
57
 
56
- def save_represented_object(save_method)
58
+ def save_or_destroy_represented_object(save_method)
57
59
  return true if represented_object.blank?
58
60
  call_save_or_destroy(represented_object, save_method)
59
61
  end
62
+
63
+ def self.included(base)
64
+ base.extend(ClassMethods)
65
+ end
66
+
67
+ module ClassMethods
68
+
69
+ def all(*args)
70
+ represented_object_class.all(*args).map { |represented_object| self.new({}, represented_object) }
71
+ end
72
+
73
+ end
60
74
 
61
75
  private #################### PRIVATE METHODS DOWN BELOW ######################
62
76
 
@@ -64,9 +78,5 @@ module ObjectAttorney
64
78
  object.respond_to?(:marked_for_destruction?) ? object.marked_for_destruction? : false
65
79
  end
66
80
 
67
- def evoke_method_on_object(object, method)
68
- object.send(method)
69
- end
70
-
71
81
  end
72
82
  end
@@ -16,15 +16,15 @@ module ObjectAttorney
16
16
 
17
17
  def destroy(options = {})
18
18
  return true if represented_object.blank?
19
- evoke_method_on_object(represented_object, :destroy, options)
19
+ represented_object.destroy(options).ok?
20
20
  end
21
21
 
22
22
  def call_save_or_destroy(object, save_method, options = {})
23
23
  if object == self || object == represented_object
24
- represented_object.present? ? evoke_method_on_object(represented_object, save_method, options) : true
24
+ represented_object.present? ? represented_object.send(save_method, options).ok? : true
25
25
  else
26
26
  save_method = :destroy if check_if_marked_for_destruction?(object)
27
- evoke_method_on_object(object, save_method, options)
27
+ object.send(save_method, options).ok?
28
28
  end
29
29
  end
30
30
 
@@ -35,26 +35,31 @@ module ObjectAttorney
35
35
  end
36
36
 
37
37
  def submit(save_method, options = {})
38
- save_result = save_represented_object(save_method, options)
39
- save_result = save_nested_objects(save_method) if save_result
38
+ save_result = save_or_destroy_nested_objects(save_method, :belongs_to, options)
39
+ save_result = save_or_destroy_represented_object(save_method, options) if save_result
40
+ save_result = save_or_destroy_nested_objects(save_method, :has_many, options) if save_result
41
+ save_result = save_or_destroy_nested_objects(save_method, :has_one, options) if save_result
40
42
  save_result
41
43
  end
42
44
 
43
- def save_represented_object(save_method, options = {})
45
+ def save_or_destroy_represented_object(save_method, options = {})
44
46
  return true if represented_object.blank?
45
- call_save_or_destroy(represented_object, save_method, options).ok?
47
+ call_save_or_destroy(represented_object, save_method, options)
46
48
  end
49
+
50
+ def save_or_destroy_nested_objects(save_method, association_macro, options = {})
51
+ nested_objects(association_macro).map do |reflection, nested_object|
52
+
53
+ populate_foreign_key(self, nested_object, reflection, :has_many)
54
+ populate_foreign_key(self, nested_object, reflection, :has_one)
47
55
 
48
- def save_nested_objects(save_method, options = {})
49
- nested_objects.map do |nested_object|
50
- call_save_or_destroy(nested_object, save_method, options).ok?
51
- end.all?
52
- end
56
+ saving_result = call_save_or_destroy(nested_object, save_method, options)
53
57
 
54
- private #################### PRIVATE METHODS DOWN BELOW ######################
58
+ populate_foreign_key(nested_object, self, reflection, :belongs_to)
55
59
 
56
- def evoke_method_on_object(object, method, options = {})
57
- object.send(method, options)
60
+ saving_result
61
+
62
+ end.all?
58
63
  end
59
64
 
60
65
  end
@@ -0,0 +1,35 @@
1
+ module ObjectAttorney
2
+
3
+ class Reflection
4
+ attr_reader :name, :klass, :options
5
+
6
+ def initialize(class_name, options)
7
+ options = options.is_a?(Hash) ? options : {}
8
+
9
+ @name, @options = class_name, options
10
+
11
+ @klass = options[:class_name] || klass_default(@name)
12
+ @klass = @klass.constantize if @klass.is_a?(String)
13
+ end
14
+
15
+ def single_name
16
+ @single_name ||= options[:single_name] || name.to_s.singularize
17
+ end
18
+
19
+ def plural_name
20
+ @plural_name ||= options[:plural_name] || name.to_s.pluralize
21
+ end
22
+
23
+ private ################################# private
24
+
25
+ def klass_default(class_name)
26
+ if Helpers.plural?(class_name)
27
+ class_name.to_s.singularize.camelize
28
+ else
29
+ class_name.to_s.camelize
30
+ end
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -1,3 +1,3 @@
1
1
  module ObjectAttorney
2
- VERSION = "1.2.1"
2
+ VERSION = "2.1.1"
3
3
  end
@@ -1,29 +1,26 @@
1
1
  require "object_attorney/version"
2
2
  require "object_attorney/helpers"
3
- require "object_attorney/association_reflection"
3
+ require "object_attorney/reflection"
4
4
  require "object_attorney/nested_objects"
5
5
  require "object_attorney/orm"
6
6
  require 'active_record'
7
7
 
8
8
  module ObjectAttorney
9
9
 
10
- def initialize(attributes = {}, object = nil, options = {})
11
- if !attributes.kind_of?(Hash) && object.blank?
10
+ def initialize(attributes = {}, object = nil)
11
+ if !attributes.is_a?(Hash) && object.blank?
12
12
  object = attributes
13
13
  attributes = nil
14
14
  end
15
15
 
16
16
  attributes = {} if attributes.blank?
17
17
 
18
- if !attributes.include?("id") && object.kind_of?(String)
19
- attributes["id"] = object
20
- object = nil
21
- end
22
-
23
18
  @represented_object = object if object.present?
24
19
 
25
20
  assign_attributes attributes
26
21
  mark_for_destruction_if_necessary(self, attributes)
22
+
23
+ init(attributes)
27
24
  end
28
25
 
29
26
  def assign_attributes(attributes = {})
@@ -38,10 +35,17 @@ module ObjectAttorney
38
35
  respond_to?(attribute) ? send(attribute) : nil
39
36
  end
40
37
 
38
+ def send_to_representative(method_name, *args)
39
+ return false if represented_object.blank?
40
+
41
+ represented_object.send(method_name, *args)
42
+ end
43
+
41
44
  protected #################### PROTECTED METHODS DOWN BELOW ######################
42
45
 
46
+ def init(attributes); end
47
+
43
48
  def allowed_attribute(attribute)
44
- attribute = attribute.to_s
45
49
  respond_to?("#{attribute}=")
46
50
  end
47
51
 
@@ -86,9 +90,13 @@ module ObjectAttorney
86
90
  module ClassMethods
87
91
 
88
92
  def represents(represented_object_name, options = {})
89
- self.instance_variable_set("@represented_object_reflection", AssociationReflection.new(represented_object_name, options))
93
+ self.instance_variable_set("@represented_object_reflection", Reflection.new(represented_object_name, options))
90
94
 
91
95
  define_method(represented_object_name) { represented_object }
96
+
97
+ if options.include?(:properties)
98
+ delegate_properties(*options[:properties], to: represented_object_name)
99
+ end
92
100
  end
93
101
 
94
102
  def represented_object_reflection
@@ -0,0 +1,52 @@
1
+ require "spec_helper"
2
+
3
+
4
+ shared_examples "a BulkPostsForm" do
5
+
6
+ it "1. Creating multiple 'Post's, with a tabless model 'BulkPostsForm' has if it had 'accepts_nested_attributes_for :posts'" do
7
+ params = {
8
+ bulk_post: {
9
+ posts_attributes: {
10
+ "0" => { title: "My title1" },
11
+ "1" => { title: "My title2" }
12
+ }
13
+ }
14
+ }
15
+
16
+ described_class.new(params[:bulk_post]).save
17
+
18
+ Post.all.count.should == 2
19
+ end
20
+
21
+ it "2. Creating new 'Post', editing another and deleting yet another." do
22
+ params = {
23
+ bulk_post: {
24
+ posts_attributes: {
25
+ "0" => { title: "new post" },
26
+ "2" => { id: 1, title: 'altered post' },
27
+ "1" => { id: 2, title: 'to be destroyed', _destroy: true }
28
+ }
29
+ }
30
+ }
31
+
32
+ Post.create(title: "My title1")
33
+ Post.create(title: "My title2")
34
+ Post.all.count.should == 2
35
+ Post.first.title.should == 'My title1'
36
+
37
+ described_class.new(params[:bulk_post]).save
38
+
39
+ Post.all.count.should == 2
40
+ Post.find_by_id(1).title.should == 'altered post'
41
+ Post.find_by_id(3).title.should == 'new post'
42
+ end
43
+
44
+ end
45
+
46
+ describe BulkPostsForm::Base do
47
+ it_behaves_like 'a BulkPostsForm'
48
+ end
49
+
50
+ describe BulkPostsForm::Explicit do
51
+ it_behaves_like 'a BulkPostsForm'
52
+ end
@@ -0,0 +1,37 @@
1
+ require "spec_helper"
2
+
3
+ shared_examples "a BulkPostsAllowOnlyExistingForm" do
4
+
5
+ it "1. Tabless model 'BulkPostsAllowOnlyNew' only accepts editings 'Post' requests and ignores new requests." do
6
+ params = {
7
+ bulk_post: {
8
+ posts_attributes: {
9
+ "0" => { title: "new post" },
10
+ "1" => { id: 1, title: 'altered post' },
11
+ "2" => { id: 2, title: '', _destroy: true }
12
+ }
13
+ }
14
+ }
15
+
16
+ Post.create(title: "My title1")
17
+ Post.create(title: "My title2")
18
+ Post.all.count.should == 2
19
+ Post.find_by_id(1).title.should == 'My title1'
20
+ Post.find_by_id(2).title.should == 'My title2'
21
+
22
+ buld_posts_form = described_class.new(params[:bulk_post])
23
+ buld_posts_form.save
24
+
25
+ Post.all.count.should == 1
26
+ Post.first.title.should == 'altered post'
27
+ end
28
+
29
+ end
30
+
31
+ describe BulkPostsAllowOnlyExistingForm::Base do
32
+ it_behaves_like 'a BulkPostsAllowOnlyExistingForm'
33
+ end
34
+
35
+ describe BulkPostsAllowOnlyExistingForm::Explicit do
36
+ it_behaves_like 'a BulkPostsAllowOnlyExistingForm'
37
+ end