active_presenter 0.0.6 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,7 +3,7 @@ module ActivePresenter
3
3
  #
4
4
  class Base
5
5
  include ActiveSupport::Callbacks
6
- define_callbacks :before_save, :after_save
6
+ define_callbacks :before_validation, :before_save, :after_save
7
7
 
8
8
  class_inheritable_accessor :presented
9
9
  self.presented = {}
@@ -36,8 +36,6 @@ module ActivePresenter
36
36
  attribute_name.to_s.gsub("#{presentable_type}_", "").humanize
37
37
  end
38
38
 
39
- attr_accessor :errors
40
-
41
39
  # Accepts arguments in two forms. For example, if you had a SignupPresenter that presented User, and Account, you could specify arguments in the following two forms:
42
40
  #
43
41
  # 1. SignupPresenter.new(:user_login => 'james', :user_password => 'swordfish', :user_password_confirmation => 'swordfish', :account_subdomain => 'giraffesoft')
@@ -60,10 +58,26 @@ module ActivePresenter
60
58
  self.attributes = args
61
59
  end
62
60
 
63
- # Set the attributes of the presentable instances using the type_attribute form (i.e. user_login => 'james')
61
+ # Set the attributes of the presentable instances using
62
+ # the type_attribute form (i.e. user_login => 'james'), or
63
+ # the multiparameter attribute form (i.e. {user_birthday(1i) => "1980", user_birthday(2i) => "3"})
64
64
  #
65
65
  def attributes=(attrs)
66
- attrs.each { |k,v| send("#{k}=", v) unless attribute_protected?(k)}
66
+ multi_parameter_attributes = {}
67
+
68
+ attrs.each do |k,v|
69
+ if (base_attribute = k.to_s.split("(").first) != k.to_s
70
+ presentable = presentable_for(base_attribute)
71
+ multi_parameter_attributes[presentable] ||= {}
72
+ multi_parameter_attributes[presentable].merge!(flatten_attribute_name(k,presentable).to_sym => v)
73
+ else
74
+ send("#{k}=", v) unless attribute_protected?(k)
75
+ end
76
+ end
77
+
78
+ multi_parameter_attributes.each do |presentable,multi_attrs|
79
+ send(presentable).send(:attributes=, multi_attrs)
80
+ end
67
81
  end
68
82
 
69
83
  # Makes sure that the presenter is accurate about responding to presentable's attributes, even though they are handled by method_missing.
@@ -87,13 +101,17 @@ module ActivePresenter
87
101
  # Returns boolean based on the validity of the presentables by calling valid? on each of them.
88
102
  #
89
103
  def valid?
90
- presented.keys.each do |type|
91
- presented_inst = send(type)
92
-
93
- merge_errors(presented_inst, type) unless presented_inst.valid?
104
+ errors.clear
105
+ if run_callbacks_with_halt(:before_validation)
106
+ presented.keys.each do |type|
107
+ presented_inst = send(type)
108
+
109
+ next unless save?(type, presented_inst)
110
+ merge_errors(presented_inst, type) unless presented_inst.valid?
111
+ end
112
+
113
+ errors.empty?
94
114
  end
95
-
96
- errors.empty?
97
115
  end
98
116
 
99
117
  # Save all of the presentables, wrapped in a transaction.
@@ -105,29 +123,31 @@ module ActivePresenter
105
123
 
106
124
  ActiveRecord::Base.transaction do
107
125
  if valid? && run_callbacks_with_halt(:before_save)
108
- saved = presented_instances.map { |i| i.save(false) }.all?
126
+ saved = presented.keys.select {|key| save?(key, send(key))}.all? {|key| send(key).save}
109
127
  raise ActiveRecord::Rollback unless saved # TODO: Does this happen implicitly?
110
128
  end
129
+
130
+ run_callbacks_with_halt(:after_save) if saved
111
131
  end
112
132
 
113
- run_callbacks_with_halt(:after_save) if saved
114
-
115
133
  saved
116
134
  end
117
135
 
118
- # Save all of the presentables, by calling each of their save! methods, wrapped in a transaction.
136
+ # Save all of the presentables wrapped in a transaction.
119
137
  #
120
138
  # Returns true on success, will raise otherwise.
121
139
  #
122
140
  def save!
141
+ raise ActiveRecord::RecordInvalid.new(self) unless valid?
123
142
  raise ActiveRecord::RecordNotSaved unless run_callbacks_with_halt(:before_save)
124
143
 
125
144
  ActiveRecord::Base.transaction do
126
- valid? # collect errors before potential exception raise
127
- presented_instances.each { |i| i.save! }
145
+ presented.keys.select {|key| save?(key, send(key))}.each {|key| send(key).save!}
146
+
147
+ run_callbacks_with_halt(:after_save)
128
148
  end
129
-
130
- run_callbacks_with_halt(:after_save)
149
+
150
+ true
131
151
  end
132
152
 
133
153
  # Update attributes, and save the presentables
@@ -139,6 +159,21 @@ module ActivePresenter
139
159
  save
140
160
  end
141
161
 
162
+ # Should this presented instance be saved? By default, this returns true
163
+ # Called from #save and #save!
164
+ #
165
+ # For
166
+ # class SignupPresenter < ActivePresenter::Base
167
+ # presents :account, :user
168
+ # end
169
+ #
170
+ # #save? will be called twice:
171
+ # save?(:account, #<Account:0x1234dead>)
172
+ # save?(:user, #<User:0xdeadbeef>)
173
+ def save?(presented_key, presented_instance)
174
+ true
175
+ end
176
+
142
177
  # We define #id and #new_record? to play nice with form_for(@presenter) in Rails
143
178
  def id # :nodoc:
144
179
  nil
@@ -147,7 +182,7 @@ module ActivePresenter
147
182
  def new_record?
148
183
  true
149
184
  end
150
-
185
+
151
186
  protected
152
187
  def presented_instances
153
188
  presented.keys.map { |key| send(key) }
@@ -1,8 +1,8 @@
1
1
  module ActivePresenter
2
2
  module VERSION
3
- MAJOR = 0
4
- MINOR = 0
5
- TINY = 6
3
+ MAJOR = 1
4
+ MINOR = 1
5
+ TINY = 2
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
data/test/base_test.rb CHANGED
@@ -91,18 +91,18 @@ Expectations do
91
91
  end
92
92
 
93
93
  expect ActiveRecord::Base.to.receive(:transaction) do
94
- s = SignupPresenter.new
94
+ s = SignupPresenter.new(:user_login => "da", :user_password => "seekrit")
95
95
  s.save!
96
96
  end
97
97
 
98
98
  expect User.any_instance.to.receive(:save!) do
99
- s = SignupPresenter.new
99
+ s = SignupPresenter.new(:user_login => "da", :user_password => "seekrit")
100
100
  s.save!
101
101
  end
102
102
 
103
103
  expect Account.any_instance.to.receive(:save!) do
104
104
  User.any_instance.stubs(:save!)
105
- s = SignupPresenter.new
105
+ s = SignupPresenter.new(:user_login => "da", :user_password => "seekrit")
106
106
  s.save!
107
107
  end
108
108
 
@@ -130,7 +130,21 @@ Expectations do
130
130
  s.update_attributes :user_login => 'Something Different'
131
131
  s.user_login
132
132
  end
133
-
133
+
134
+ # Multiparameter assignment
135
+ expect Time.parse('March 27 1980 9:30:59 am') do
136
+ s = SignupPresenter.new
137
+ s.update_attributes({
138
+ :"user_birthday(1i)" => '1980',
139
+ :"user_birthday(2i)" => '3',
140
+ :"user_birthday(3i)" => '27',
141
+ :"user_birthday(4i)" => '9',
142
+ :"user_birthday(5i)" => '30',
143
+ :"user_birthday(6i)" => '59'
144
+ })
145
+ s.user_birthday
146
+ end
147
+
134
148
  # this is a regression test to make sure that _title is working. we had a weird conflict with using String#delete
135
149
  expect 'something' do
136
150
  s = SignupPresenter.new :account_title => 'something'
@@ -164,6 +178,17 @@ Expectations do
164
178
  expect NoMethodError do
165
179
  SignupPresenter.new({:i_dont_exist=>"blah"})
166
180
  end
181
+
182
+ # ActiveRecord::Base uses nil id to signify an unsaved model
183
+ expect nil do
184
+ SignupPresenter.new.id
185
+ end
186
+
187
+ expect nil do
188
+ returning(SignupPresenter.new(:user => User.new(hash_for_user))) do |presenter|
189
+ presenter.save!
190
+ end.id
191
+ end
167
192
 
168
193
  expect CantSavePresenter.new.not.to.be.save # it won't save because the filter chain will halt
169
194
 
@@ -185,4 +210,71 @@ Expectations do
185
210
 
186
211
  expect SamePrefixPresenter.new.to.be.respond_to?(:account_title)
187
212
  expect SamePrefixPresenter.new.to.be.respond_to?(:account_info_info)
213
+
214
+ expect [:before_validation, :before_save, :after_save] do
215
+ returning(CallbackOrderingPresenter.new) do |presenter|
216
+ presenter.save!
217
+ end.steps
218
+ end
219
+
220
+ expect [:before_validation, :before_save] do
221
+ returning(CallbackCantSavePresenter.new) do |presenter|
222
+ presenter.save
223
+ end.steps
224
+ end
225
+
226
+ expect [:before_validation, :before_save] do
227
+ returning(CallbackCantSavePresenter.new) do |presenter|
228
+ begin
229
+ presenter.save!
230
+ rescue ActiveRecord::RecordNotSaved
231
+ # NOP
232
+ end
233
+ end.steps
234
+ end
235
+
236
+ expect ActiveRecord::RecordNotSaved do
237
+ CallbackCantSavePresenter.new.save!
238
+ end
239
+
240
+ expect ActiveRecord::RecordInvalid do
241
+ CallbackCantValidatePresenter.new.save!
242
+ end
243
+
244
+ expect [:before_validation] do
245
+ returning(CallbackCantValidatePresenter.new) do |presenter|
246
+ begin
247
+ presenter.save!
248
+ rescue ActiveRecord::RecordInvalid
249
+ # NOP
250
+ end
251
+ end.steps
252
+ end
253
+
254
+ expect [:before_validation] do
255
+ returning(CallbackCantValidatePresenter.new) do |presenter|
256
+ presenter.save
257
+ end.steps
258
+ end
259
+
260
+ expect ActiveRecord::Errors.any_instance.to.receive(:clear) do
261
+ CallbackCantValidatePresenter.new.valid?
262
+ end
263
+
264
+ # this should act as ActiveRecord models do
265
+ expect NoMethodError do
266
+ SignupPresenter.new({:i_dont_exist=>"blah"})
267
+ end
268
+
269
+ expect false do
270
+ SignupNoNilPresenter.new.save
271
+ end
272
+
273
+ expect true do
274
+ SignupNoNilPresenter.new(:user => nil, :account => Account.new).save
275
+ end
276
+
277
+ expect true do
278
+ SignupNoNilPresenter.new(:user => nil, :account => Account.new).save!
279
+ end
188
280
  end
data/test/test_helper.rb CHANGED
@@ -10,9 +10,10 @@ ActiveRecord::Base.logger.level = Logger::WARN
10
10
 
11
11
  ActiveRecord::Schema.define(:version => 0) do
12
12
  create_table :users do |t|
13
- t.boolean :admin, :default => false
14
- t.string :login, :default => ''
15
- t.string :password, :default => ''
13
+ t.boolean :admin, :default => false
14
+ t.string :login, :default => ''
15
+ t.string :password, :default => ''
16
+ t.datetime :birthday
16
17
  end
17
18
 
18
19
  create_table :accounts do |t|
@@ -31,7 +32,7 @@ end
31
32
 
32
33
  class User < ActiveRecord::Base
33
34
  validates_presence_of :login, :password
34
- attr_accessible :login, :password
35
+ attr_accessible :login, :password, :birthday
35
36
  attr_accessor :password_confirmation
36
37
  end
37
38
  class Account < ActiveRecord::Base; end
@@ -54,6 +55,14 @@ class CantSavePresenter < ActivePresenter::Base
54
55
  def halt; false; end
55
56
  end
56
57
 
58
+ class SignupNoNilPresenter < ActivePresenter::Base
59
+ presents :account, :user
60
+
61
+ def save?(key, instance)
62
+ !instance.nil?
63
+ end
64
+ end
65
+
57
66
  class AfterSavePresenter < ActivePresenter::Base
58
67
  presents :address
59
68
 
@@ -68,6 +77,97 @@ class SamePrefixPresenter < ActivePresenter::Base
68
77
  presents :account, :account_info
69
78
  end
70
79
 
80
+ class CallbackOrderingPresenter < ActivePresenter::Base
81
+ presents :account
82
+
83
+ before_validation :do_before_validation
84
+ before_save :do_before_save
85
+ after_save :do_after_save
86
+
87
+ attr_reader :steps
88
+
89
+ def initialize(params={})
90
+ super
91
+ @steps = []
92
+ end
93
+
94
+ def do_before_validation
95
+ @steps << :before_validation
96
+ end
97
+
98
+ def do_before_save
99
+ @steps << :before_save
100
+ end
101
+
102
+ def do_after_save
103
+ @steps << :after_save
104
+ end
105
+ end
106
+
107
+ class CallbackCantSavePresenter < ActivePresenter::Base
108
+ presents :account
109
+
110
+ before_validation :do_before_validation
111
+ before_save :do_before_save
112
+ before_save :halt
113
+ after_save :do_after_save
114
+
115
+ attr_reader :steps
116
+
117
+ def initialize(params={})
118
+ super
119
+ @steps = []
120
+ end
121
+
122
+ def do_before_validation
123
+ @steps << :before_validation
124
+ end
125
+
126
+ def do_before_save
127
+ @steps << :before_save
128
+ end
129
+
130
+ def do_after_save
131
+ @steps << :after_save
132
+ end
133
+
134
+ def halt
135
+ false
136
+ end
137
+ end
138
+
139
+ class CallbackCantValidatePresenter < ActivePresenter::Base
140
+ presents :account
141
+
142
+ before_validation :do_before_validation
143
+ before_validation :halt
144
+ before_save :do_before_save
145
+ after_save :do_after_save
146
+
147
+ attr_reader :steps
148
+
149
+ def initialize(params={})
150
+ super
151
+ @steps = []
152
+ end
153
+
154
+ def do_before_validation
155
+ @steps << :before_validation
156
+ end
157
+
158
+ def do_before_save
159
+ @steps << :before_save
160
+ end
161
+
162
+ def do_after_save
163
+ @steps << :after_save
164
+ end
165
+
166
+ def halt
167
+ false
168
+ end
169
+ end
170
+
71
171
  def hash_for_user(opts = {})
72
172
  {:login => 'jane', :password => 'seekrit' }.merge(opts)
73
173
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_presenter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Golick & Daniel Haran
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-01-22 00:00:00 -05:00
12
+ date: 2009-03-13 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies: []
15
15