active_presenter 0.0.6 → 1.1.2

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.
@@ -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