galetahub-active_presenter 0.1.0

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.
@@ -0,0 +1,86 @@
1
+ = ActivePresenter
2
+
3
+ ActivePresenter is the presenter library you already know! (...if you know ActiveRecord)
4
+
5
+ By acting nearly identically to ActiveRecord models, ActivePresenter makes presenters highly approachable to anybody who is already familiar with ActiveRecord.
6
+
7
+ == Get It
8
+
9
+ As a gem (Gemfile):
10
+
11
+ gem 'active_presenter', :git => 'git://github.com/galetahub/active_presenter.git'
12
+
13
+ As a rails plugin:
14
+
15
+ rails install plugin git://github.com/galetahub/active_presenter.git
16
+
17
+ Or get the source from github:
18
+
19
+ $ git clone git://github.com/galetahub/active_presenter.git
20
+
21
+ == Usage
22
+
23
+ Creating a presenter is as simple as subclassing ActivePresenter::Base. Use the presents method to indicate which models the presenter should present.
24
+
25
+ class SignupPresenter < ActivePresenter::Base
26
+ presents :user, :account
27
+ end
28
+
29
+ In the above example, :user will (predictably) become User. If you want to override this behaviour, specify the desired types in a hash, as so:
30
+
31
+ class PresenterWithTwoAddresses < ActivePresenter::Base
32
+ presents :primary_address => Address, :secondary_address => Address
33
+ end
34
+
35
+ === Instantiation
36
+
37
+ Then, you can instantiate the presenter using either, or both of two forms.
38
+
39
+ For example, if you had a SignupPresenter that presented User, and Account, you could specify arguments in the following two forms:
40
+
41
+ 1. SignupPresenter.new(:user_login => 'james', :user_password => 'swordfish', :user_password_confirmation => 'swordfish', :account_subdomain => 'giraffesoft')
42
+
43
+ - This form is useful for initializing a new presenter from the params hash: i.e. SignupPresenter.new(params[:signup_presenter])
44
+
45
+ 2. SignupPresenter.new(:user => User.find(1), :account => Account.find(2))
46
+
47
+ - This form is useful if you have instances that you'd like to edit using the presenter. You can subsequently call presenter.update_attributes(params[:signup_presenter]) just like with a regular AR instance.
48
+
49
+ Both forms can also be mixed together: SignupPresenter.new(:user => User.find(1), :user_login => 'james').
50
+
51
+ In this case, the login attribute will be updated on the user instance provided.
52
+
53
+ If you don't specify an instance, one will be created by calling Model.new
54
+
55
+ === Validation
56
+
57
+ The #valid? method will return true or false based on the validity of the presented objects.
58
+
59
+ This is calculated by calling #valid? on them.
60
+
61
+ You can retrieve the errors in two ways.
62
+
63
+ 1. By calling #errors on the presenter, which returns an instance of ActiveRecord::Errors where all the attributes are in type_name_attribute_name form (i.e. You'd retrieve an error on User#login, by calling @presenter.errors.on(:user_login)).
64
+
65
+ 2. By calling @presenter.user_errors, or @presenter.user.errors to retrieve the errors from one presentable.
66
+
67
+ Both of these methods are compatible with error_messages_for. It just depends whether you'd like to show all the errors in one block, or whether you'd prefer to break them up.
68
+
69
+ === Saving
70
+
71
+ You can save your presenter the same way you'd save an ActiveRecord object. Both #save, and #save! behave the same way they do on a normal AR model.
72
+
73
+ === Callbacks
74
+
75
+ Callbacks work exactly like ActiveRecord callbacks. before_save, and after_save are available.
76
+
77
+ Note that if any of your after_save callbacks return false, the rest of them will not be run. This is consistent with AR behavior.
78
+
79
+ == Credits
80
+
81
+ ActivePresenter was created, and is maintained by {Daniel Haran}[http://danielharan.com] and {James Golick}[http://jamesgolick.com] on the train ride to {RubyFringe}[http://rubyfringe.com] from Montreal.
82
+
83
+ == License
84
+
85
+ ActivePresenter is available under the {MIT License}[http://en.wikipedia.org/wiki/MIT_License]
86
+
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+
6
+ desc 'Default: run unit tests.'
7
+ task :default => :test
8
+
9
+ desc 'Test the active_presenter plugin.'
10
+ Rake::TestTask.new(:test) do |t|
11
+ t.libs << 'lib'
12
+ t.libs << 'test'
13
+ t.pattern = 'test/**/*_test.rb'
14
+ t.verbose = true
15
+ end
16
+
17
+ desc 'Generate documentation for the active_presenter plugin.'
18
+ Rake::RDocTask.new(:rdoc) do |rdoc|
19
+ rdoc.rdoc_dir = 'rdoc'
20
+ rdoc.title = 'ActivePresenter'
21
+ rdoc.options << '--line-numbers' << '--inline-source'
22
+ rdoc.rdoc_files.include('README')
23
+ rdoc.rdoc_files.include('lib/**/*.rb')
24
+ end
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'active_record'
3
+
4
+ module ActivePresenter
5
+ autoload :Base, 'active_presenter/base'
6
+ autoload :Version, 'active_presenter/version'
7
+ end
@@ -0,0 +1,282 @@
1
+ module ActivePresenter
2
+ # Base class for presenters. See README for usage.
3
+ #
4
+ class Base
5
+ extend ActiveModel::Callbacks
6
+ extend ActiveModel::Naming
7
+ extend ActiveModel::Translation
8
+
9
+ include ActiveModel::Conversion
10
+ include ActiveModel::MassAssignmentSecurity
11
+
12
+ attr_reader :errors
13
+
14
+ define_model_callbacks :validation, :save
15
+
16
+ class_inheritable_accessor :presented
17
+ self.presented = {}
18
+
19
+ # Indicates which models are to be presented by this presenter.
20
+ # i.e.
21
+ #
22
+ # class SignupPresenter < ActivePresenter::Base
23
+ # presents :user, :account
24
+ # end
25
+ #
26
+ # In the above example, :user will (predictably) become User. If you want to override this behaviour, specify the desired types in a hash, as so:
27
+ #
28
+ # class PresenterWithTwoAddresses < ActivePresenter::Base
29
+ # presents :primary_address => Address, :secondary_address => Address
30
+ # end
31
+ #
32
+ class << self
33
+ def presents(*types)
34
+ types_and_classes = types.extract_options!
35
+ types.each { |t| types_and_classes[t] = t.to_s.tableize.classify.constantize }
36
+
37
+ attr_accessor *types_and_classes.keys
38
+
39
+ types_and_classes.keys.each do |t|
40
+ define_method("#{t}_errors") do
41
+ send(t).errors
42
+ end
43
+
44
+ presented[t] = types_and_classes[t]
45
+ end
46
+ end
47
+
48
+ def human_attribute_name(attribute_key_name, options = {})
49
+ presentable_type = presented.keys.detect do |type|
50
+ attribute_key_name.to_s.starts_with?("#{type}_") || attribute_key_name.to_s == type.to_s
51
+ end
52
+ attribute_key_name_without_class = attribute_key_name.to_s.gsub("#{presentable_type}_", "")
53
+
54
+ if presented[presentable_type] and attribute_key_name_without_class != presentable_type.to_s
55
+ presented[presentable_type].human_attribute_name(attribute_key_name_without_class, options)
56
+ else
57
+ I18n.translate(presentable_type, options.merge(:default => presentable_type.to_s.humanize, :scope => [:activerecord, :models]))
58
+ end
59
+ end
60
+
61
+ # Since ActivePresenter does not descend from ActiveRecord, we need to
62
+ # mimic some ActiveRecord behavior in order for the ActiveRecord::Errors
63
+ # object we're using to work properly.
64
+ #
65
+ # This problem was introduced with Rails 2.3.4.
66
+ # Fix courtesy http://gist.github.com/191263
67
+ def self_and_descendants_from_active_record # :nodoc:
68
+ [self]
69
+ end
70
+
71
+ def human_name(options = {}) # :nodoc:
72
+ defaults = self_and_descendants_from_active_record.map do |klass|
73
+ :"#{klass.name.underscore}"
74
+ end
75
+ defaults << self.name.humanize
76
+ I18n.translate(defaults.shift, {:scope => [:activerecord, :models], :count => 1, :default => defaults}.merge(options))
77
+ end
78
+ end
79
+
80
+ # 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:
81
+ #
82
+ # 1. SignupPresenter.new(:user_login => 'james', :user_password => 'swordfish', :user_password_confirmation => 'swordfish', :account_subdomain => 'giraffesoft')
83
+ # - This form is useful for initializing a new presenter from the params hash: i.e. SignupPresenter.new(params[:signup_presenter])
84
+ # 2. SignupPresenter.new(:user => User.find(1), :account => Account.find(2))
85
+ # - This form is useful if you have instances that you'd like to edit using the presenter. You can subsequently call presenter.update_attributes(params[:signup_presenter]) just like with a regular AR instance.
86
+ #
87
+ # Both forms can also be mixed together: SignupPresenter.new(:user => User.find(1), :user_login => 'james')
88
+ # In this case, the login attribute will be updated on the user instance provided.
89
+ #
90
+ # If you don't specify an instance, one will be created by calling Model.new
91
+ #
92
+ def initialize(args = {})
93
+ @errors = ActiveModel::Errors.new(self)
94
+ return self unless args
95
+ presented.each do |type, klass|
96
+ value = args.delete(type)
97
+ send("#{type}=", value.is_a?(klass) ? value : klass.new)
98
+ end
99
+ self.attributes = args
100
+ end
101
+
102
+ # Set the attributes of the presentable instances using
103
+ # the type_attribute form (i.e. user_login => 'james'), or
104
+ # the multiparameter attribute form (i.e. {user_birthday(1i) => "1980", user_birthday(2i) => "3"})
105
+ #
106
+ def attributes=(attrs)
107
+ return if attrs.nil?
108
+
109
+ attrs = attrs.stringify_keys
110
+ multi_parameter_attributes = {}
111
+
112
+ sanitize_for_mass_assignment(attrs).each do |k,v|
113
+ if (base_attribute = k.to_s.split("(").first) != k.to_s
114
+ presentable = presentable_for(base_attribute)
115
+ multi_parameter_attributes[presentable] ||= {}
116
+ multi_parameter_attributes[presentable].merge!(flatten_attribute_name(k,presentable).to_sym => v)
117
+ else
118
+ send("#{k}=", v) unless attribute_protected?(k)
119
+ end
120
+ end
121
+
122
+ multi_parameter_attributes.each do |presentable,multi_attrs|
123
+ send(presentable).send(:attributes=, multi_attrs)
124
+ end
125
+ end
126
+
127
+ # Makes sure that the presenter is accurate about responding to presentable's attributes, even though they are handled by method_missing.
128
+ #
129
+ def respond_to?(method, include_private = false)
130
+ presented_attribute?(method) || super
131
+ end
132
+
133
+ # Handles the decision about whether to delegate getters and setters to presentable instances.
134
+ #
135
+ def method_missing(method_name, *args, &block)
136
+ presented_attribute?(method_name) ? delegate_message(method_name, *args, &block) : super
137
+ end
138
+
139
+ # Returns boolean based on the validity of the presentables by calling valid? on each of them.
140
+ #
141
+ def valid?
142
+ validated = false
143
+ errors.clear
144
+ result = _run_validation_callbacks do
145
+ presented.each do |type, klass|
146
+ presented_inst = (send(type) || klass.new)
147
+ next unless save?(type, presented_inst)
148
+ merge_errors(presented_inst, type) unless presented_inst.valid?
149
+ end
150
+ validated = true
151
+ end
152
+ errors.empty? && validated
153
+ end
154
+
155
+ # Do any of the attributes have unsaved changes?
156
+ def changed?
157
+ presented_instances.map(&:changed?).any?
158
+ end
159
+
160
+ # Save all of the presentables, wrapped in a transaction.
161
+ #
162
+ # Returns true or false based on success.
163
+ #
164
+ def save(options={})
165
+ saved = false
166
+ ActiveRecord::Base.transaction do
167
+ if !perform_validations?(options) || (perform_validations?(options) && valid?)
168
+ _run_save_callbacks do
169
+ saved = presented.keys.select {|key| save?(key, send(key))}.all? {|key| send(key).save}
170
+ raise ActiveRecord::Rollback unless saved
171
+ end
172
+ end
173
+ end
174
+ saved
175
+ end
176
+
177
+ # Save all of the presentables wrapped in a transaction.
178
+ #
179
+ # Returns true on success, will raise otherwise.
180
+ #
181
+ def save!(options={})
182
+ saved = false
183
+ ActiveRecord::Base.transaction do
184
+ raise ActiveRecord::RecordInvalid.new(self) if perform_validations?(options) && !valid?
185
+ _run_save_callbacks do
186
+ presented.keys.select {|key| save?(key, send(key))}.all? {|key| send(key).save!}
187
+ saved = true
188
+ end
189
+ raise ActiveRecord::RecordNotSaved.new(self) unless saved
190
+ end
191
+ saved
192
+ end
193
+
194
+ # Update attributes, and save the presentables
195
+ #
196
+ # Returns true or false based on success.
197
+ #
198
+ def update_attributes(attrs)
199
+ self.attributes = attrs
200
+ save
201
+ end
202
+
203
+ # Should this presented instance be saved? By default, this returns true
204
+ # Called from #save and #save!
205
+ #
206
+ # For
207
+ # class SignupPresenter < ActivePresenter::Base
208
+ # presents :account, :user
209
+ # end
210
+ #
211
+ # #save? will be called twice:
212
+ # save?(:account, #<Account:0x1234dead>)
213
+ # save?(:user, #<User:0xdeadbeef>)
214
+ def save?(presented_key, presented_instance)
215
+ true
216
+ end
217
+
218
+ # We define #id and #new_record? to play nice with form_for(@presenter) in Rails
219
+ def id # :nodoc:
220
+ nil
221
+ end
222
+
223
+ def new_record?
224
+ presented_instances.map(&:new_record?).all?
225
+ end
226
+
227
+ def persisted?
228
+ presented_instances.map(&:persisted?).all?
229
+ end
230
+
231
+ protected
232
+ def presented_instances
233
+ presented.keys.map { |key| send(key) }
234
+ end
235
+
236
+ def delegate_message(method_name, *args, &block)
237
+ presentable = presentable_for(method_name)
238
+ send(presentable).send(flatten_attribute_name(method_name, presentable), *args, &block)
239
+ end
240
+
241
+ def presentable_for(method_name)
242
+ presented.keys.sort_by { |k| k.to_s.size }.reverse.detect do |type|
243
+ method_name.to_s.starts_with?(attribute_prefix(type))
244
+ end
245
+ end
246
+
247
+ def presented_attribute?(method_name)
248
+ p = presentable_for(method_name)
249
+ !p.nil? && send(p).respond_to?(flatten_attribute_name(method_name,p))
250
+ end
251
+
252
+ def flatten_attribute_name(name, type)
253
+ name.to_s.gsub(/^#{attribute_prefix(type)}/, '')
254
+ end
255
+
256
+ def attribute_prefix(type)
257
+ "#{type}_"
258
+ end
259
+
260
+ def merge_errors(presented_inst, type)
261
+ presented_inst.errors.each do |att,msg|
262
+ if att == :base
263
+ errors.add(type, msg)
264
+ else
265
+ errors.add(attribute_prefix(type)+att.to_s, msg)
266
+ end
267
+ end
268
+ end
269
+
270
+ def attribute_protected?(name)
271
+ presentable = presentable_for(name)
272
+ return false unless presentable
273
+ #remove_att... normally takes a hash, so we use a ''
274
+ flat_attribute = {flatten_attribute_name(name, presentable) => ''}
275
+ presented[presentable].new.send(:sanitize_for_mass_assignment, flat_attribute).empty?
276
+ end
277
+
278
+ def perform_validations?(options={})
279
+ options[:validate] != false
280
+ end
281
+ end
282
+ end
@@ -0,0 +1,4 @@
1
+ # encoding: utf-8
2
+ module ActivePresenter
3
+ VERSION = "0.1.0".freeze
4
+ end
@@ -0,0 +1,362 @@
1
+ require File.dirname(__FILE__)+'/test_helper'
2
+
3
+ Expectations do
4
+ expect nil do
5
+ SignupPresenter.new.id
6
+ end
7
+
8
+ expect true do
9
+ SignupPresenter.new.new_record?
10
+ end
11
+
12
+ expect :user => User, :account => Account do
13
+ SignupPresenter.presented
14
+ end
15
+
16
+ expect User.create!(hash_for_user) do |u|
17
+ SignupPresenter.new(:user => u.expected).user
18
+ end
19
+
20
+ expect true do
21
+ SignupPresenter.new(:user => nil).user.new_record?
22
+ end
23
+
24
+ expect User do
25
+ SignupPresenter.new.user
26
+ end
27
+
28
+ expect User.any_instance.to.receive(:login=).with('james') do
29
+ SignupPresenter.new(:user_login => 'james')
30
+ end
31
+
32
+ # admin= should be protected from mass assignment
33
+ expect SignupPresenter.new.to.be.attribute_protected?(:user_admin)
34
+ expect SignupPresenter.new(:user_admin => true).user.not.to.be.admin?
35
+
36
+ expect 'mymockvalue' do
37
+ User.any_instance.stubs(:login).returns('mymockvalue')
38
+ SignupPresenter.new.user_login
39
+ end
40
+
41
+ expect User.any_instance.to.receive(:login=).with('mymockvalue') do
42
+ SignupPresenter.new.user_login = 'mymockvalue'
43
+ end
44
+
45
+ expect SignupPresenter.new.not.to.be.valid?
46
+ expect SignupPresenter.new(:user => User.new(hash_for_user)).to.be.valid?
47
+
48
+ expect ActiveRecord::Errors do
49
+ s = SignupPresenter.new
50
+ s.valid?
51
+ s.errors
52
+ end
53
+
54
+ expect ActiveRecord::Errors do
55
+ s = SignupPresenter.new
56
+ s.valid?
57
+ s.user_errors
58
+ end
59
+
60
+ expect ActiveRecord::Errors do
61
+ s = SignupPresenter.new
62
+ s.valid?
63
+ s.account_errors
64
+ end
65
+
66
+ expect String do
67
+ s = SignupPresenter.new
68
+ s.valid?
69
+ s.errors.on(:user_login)
70
+ end
71
+
72
+ expect "can't be blank" do
73
+ s = SignupPresenter.new
74
+ s.valid?
75
+ s.errors.on(:user_login)
76
+ end
77
+
78
+ expect ["User Password can't be blank"] do
79
+ s = SignupPresenter.new(:user_login => 'login')
80
+ s.valid?
81
+ s.errors.full_messages
82
+ end
83
+
84
+ expect 'c4N n07 83 8L4nK' do
85
+ old_locale = I18n.locale
86
+ I18n.locale = '1337'
87
+
88
+ s = SignupPresenter.new(:user_login => nil)
89
+ s.valid?
90
+ message = s.errors.on(:user_login)
91
+
92
+ I18n.locale = old_locale
93
+
94
+ message
95
+ end
96
+
97
+ expect ['U53R pa22w0rD c4N n07 83 8L4nK'] do
98
+ old_locale = I18n.locale
99
+ I18n.locale = '1337'
100
+
101
+ s = SignupPresenter.new(:user_login => 'login')
102
+ s.valid?
103
+ message = s.errors.full_messages
104
+
105
+ I18n.locale = old_locale
106
+
107
+ message
108
+ end
109
+
110
+ expect ActiveRecord::Base.to.receive(:transaction) do
111
+ s = SignupPresenter.new
112
+ s.save
113
+ end
114
+
115
+ expect User.any_instance.to.receive(:save) do
116
+ s = SignupPresenter.new :user => User.new(hash_for_user)
117
+ s.save
118
+ end
119
+
120
+ expect Account.any_instance.to.receive(:save) do
121
+ s = SignupPresenter.new :user => User.new(hash_for_user)
122
+ s.save
123
+ end
124
+
125
+ expect SignupPresenter.new.not.to.be.save
126
+
127
+ expect ActiveRecord::Rollback do
128
+ ActiveRecord::Base.stubs(:transaction).yields
129
+ User.any_instance.stubs(:save).returns(false)
130
+ Account.any_instance.stubs(:save).returns(false)
131
+ s = SignupPresenter.new :user => User.new(hash_for_user)
132
+ s.save
133
+ end
134
+
135
+ expect ActiveRecord::Base.to.receive(:transaction) do
136
+ s = SignupPresenter.new(:user_login => "da", :user_password => "seekrit")
137
+ s.save!
138
+ end
139
+
140
+ expect User.any_instance.to.receive(:save!) do
141
+ s = SignupPresenter.new(:user_login => "da", :user_password => "seekrit")
142
+ s.save!
143
+ end
144
+
145
+ expect Account.any_instance.to.receive(:save!) do
146
+ User.any_instance.stubs(:save!)
147
+ s = SignupPresenter.new(:user_login => "da", :user_password => "seekrit")
148
+ s.save!
149
+ end
150
+
151
+ expect ActiveRecord::RecordInvalid do
152
+ SignupPresenter.new.save!
153
+ end
154
+
155
+ expect SignupPresenter.new(:user => User.new(hash_for_user)).to.be.save!
156
+
157
+ expect SignupPresenter.new.to.be.respond_to?(:user_login)
158
+ expect SignupPresenter.new.to.be.respond_to?(:user_password_confirmation)
159
+ expect SignupPresenter.new.to.be.respond_to?(:valid?) # just making sure i didn't break everything :)
160
+ expect SignupPresenter.new.to.be.respond_to?(:nil?, false) # making sure it's possible to pass 2 arguments
161
+
162
+ expect User.create!(hash_for_user).not.to.be.login_changed? do |user|
163
+ s = SignupPresenter.new(:user => user)
164
+ s.update_attributes :user_login => 'Something Totally Different'
165
+ end
166
+
167
+ expect SignupPresenter.new(:user => User.create!(hash_for_user)).to.receive(:save) do |s|
168
+ s.update_attributes :user_login => 'Something'
169
+ end
170
+
171
+ expect 'Something Different' do
172
+ s = SignupPresenter.new
173
+ s.update_attributes :user_login => 'Something Different'
174
+ s.user_login
175
+ end
176
+
177
+ # Multiparameter assignment
178
+ expect Time.parse('March 27 1980 9:30:59 am') do
179
+ s = SignupPresenter.new
180
+ s.update_attributes({
181
+ :"user_birthday(1i)" => '1980',
182
+ :"user_birthday(2i)" => '3',
183
+ :"user_birthday(3i)" => '27',
184
+ :"user_birthday(4i)" => '9',
185
+ :"user_birthday(5i)" => '30',
186
+ :"user_birthday(6i)" => '59'
187
+ })
188
+ s.user_birthday
189
+ end
190
+
191
+ expect nil do
192
+ s = SignupPresenter.new
193
+ s.attributes = nil
194
+ end
195
+
196
+ # this is a regression test to make sure that _title is working. we had a weird conflict with using String#delete
197
+ expect 'something' do
198
+ s = SignupPresenter.new :account_title => 'something'
199
+ s.account_title
200
+ end
201
+
202
+ expect String do
203
+ s = SignupPresenter.new
204
+ s.save
205
+ s.errors.on(:user_login)
206
+ end
207
+
208
+ expect String do
209
+ s = SignupPresenter.new
210
+ s.save! rescue
211
+ s.errors.on(:user_login)
212
+ end
213
+
214
+ expect 'Login' do
215
+ SignupPresenter.human_attribute_name(:user_login)
216
+ end
217
+
218
+ # it was raising with nil
219
+ expect SignupPresenter do
220
+ SignupPresenter.new(nil)
221
+ end
222
+
223
+ expect EndingWithSPresenter.new.address.not.to.be.nil?
224
+
225
+ # this should act as ActiveRecord models do
226
+ expect NoMethodError do
227
+ SignupPresenter.new({:i_dont_exist=>"blah"})
228
+ end
229
+
230
+ # ActiveRecord::Base uses nil id to signify an unsaved model
231
+ expect nil do
232
+ SignupPresenter.new.id
233
+ end
234
+
235
+ expect nil do
236
+ returning(SignupPresenter.new(:user => User.new(hash_for_user))) do |presenter|
237
+ presenter.save!
238
+ end.id
239
+ end
240
+
241
+ expect CantSavePresenter.new.not.to.be.save # it won't save because the filter chain will halt
242
+
243
+ expect ActiveRecord::RecordNotSaved do
244
+ CantSavePresenter.new.save!
245
+ end
246
+
247
+ expect 'Some Street' do
248
+ p = AfterSavePresenter.new
249
+ p.save
250
+ p.address.street
251
+ end
252
+
253
+ expect 'Some Street' do
254
+ p = AfterSavePresenter.new
255
+ p.save!
256
+ p.address.street
257
+ end
258
+
259
+ expect SamePrefixPresenter.new.to.be.respond_to?(:account_title)
260
+ expect SamePrefixPresenter.new.to.be.respond_to?(:account_info_info)
261
+
262
+ expect [:before_validation, :before_save, :after_save] do
263
+ returning(CallbackOrderingPresenter.new) do |presenter|
264
+ presenter.save!
265
+ end.steps
266
+ end
267
+
268
+ expect [:before_validation, :before_save] do
269
+ returning(CallbackCantSavePresenter.new) do |presenter|
270
+ presenter.save
271
+ end.steps
272
+ end
273
+
274
+ expect [:before_validation, :before_save] do
275
+ returning(CallbackCantSavePresenter.new) do |presenter|
276
+ begin
277
+ presenter.save!
278
+ rescue ActiveRecord::RecordNotSaved
279
+ # NOP
280
+ end
281
+ end.steps
282
+ end
283
+
284
+ expect ActiveRecord::RecordNotSaved do
285
+ CallbackCantSavePresenter.new.save!
286
+ end
287
+
288
+ expect ActiveRecord::RecordInvalid do
289
+ CallbackCantValidatePresenter.new.save!
290
+ end
291
+
292
+ expect [:before_validation] do
293
+ returning(CallbackCantValidatePresenter.new) do |presenter|
294
+ begin
295
+ presenter.save!
296
+ rescue ActiveRecord::RecordInvalid
297
+ # NOP
298
+ end
299
+ end.steps
300
+ end
301
+
302
+ expect [:before_validation] do
303
+ returning(CallbackCantValidatePresenter.new) do |presenter|
304
+ presenter.save
305
+ end.steps
306
+ end
307
+
308
+ expect ActiveRecord::Errors.any_instance.to.receive(:clear).twice do
309
+ CallbackCantValidatePresenter.new.valid?
310
+ end
311
+
312
+ # this should act as ActiveRecord models do
313
+ expect NoMethodError do
314
+ SignupPresenter.new({:i_dont_exist=>"blah"})
315
+ end
316
+
317
+ expect false do
318
+ SignupNoAccountPresenter.new.save
319
+ end
320
+
321
+ expect true do
322
+ SignupNoAccountPresenter.new(:user => User.new(hash_for_user), :account => nil).save
323
+ end
324
+
325
+ expect true do
326
+ SignupNoAccountPresenter.new(:user => User.new(hash_for_user), :account => nil).save!
327
+ end
328
+
329
+ expect Address do
330
+ PresenterWithTwoAddresses.new.secondary_address
331
+ end
332
+
333
+ expect "123 awesome st" do
334
+ p = PresenterWithTwoAddresses.new(:secondary_address_street => "123 awesome st")
335
+ p.save
336
+ p.secondary_address_street
337
+ end
338
+
339
+ # attr_protected
340
+ expect "" do
341
+ p = SignupPresenter.new(:account_secret => 'swordfish')
342
+ p.account.secret
343
+ end
344
+
345
+ expect "comment" do
346
+ p = HistoricalPresenter.new(:history_comment => 'comment', :user => User.new(hash_for_user))
347
+ p.save
348
+ p.history_comment
349
+ end
350
+
351
+ expect false do
352
+ SignupPresenter.new.changed?
353
+ end
354
+
355
+ expect true do
356
+ p = SignupPresenter.new(:user => User.new(hash_for_user))
357
+ p.save
358
+ p.user_login = 'something_else'
359
+ p.changed?
360
+ end
361
+
362
+ end
@@ -0,0 +1,218 @@
1
+ require File.dirname(__FILE__)+'/../lib/active_presenter' unless defined?(ActivePresenter)
2
+ require 'expectations'
3
+ require 'logger'
4
+
5
+ ActiveRecord::Base.configurations = {'sqlite3' => {:adapter => 'sqlite3', :database => ':memory:'}}
6
+ ActiveRecord::Base.establish_connection('sqlite3')
7
+
8
+ ActiveRecord::Base.logger = Logger.new(STDERR)
9
+ ActiveRecord::Base.logger.level = Logger::WARN
10
+
11
+ I18n.backend.store_translations '1337',
12
+ :activerecord => {
13
+ :models => {
14
+ :user => 'U53R'
15
+ },
16
+ :attributes => {
17
+ :user => {:password => 'pa22w0rD'}
18
+ },
19
+ :errors => {
20
+ :messages => {
21
+ :blank => 'c4N n07 83 8L4nK'
22
+ }
23
+ }
24
+ }
25
+
26
+ ActiveRecord::Schema.define(:version => 0) do
27
+ create_table :users do |t|
28
+ t.boolean :admin, :default => false
29
+ t.string :login, :default => ''
30
+ t.string :password, :default => ''
31
+ t.datetime :birthday
32
+ end
33
+
34
+ create_table :accounts do |t|
35
+ t.string :subdomain, :default => ''
36
+ t.string :title, :default => ''
37
+ t.string :secret, :default => ''
38
+ end
39
+
40
+ create_table :addresses do |t|
41
+ t.string :street
42
+ end
43
+
44
+ create_table :account_infos do |t|
45
+ t.string :info
46
+ end
47
+
48
+ create_table :histories do |t|
49
+ t.integer :user_id
50
+ t.string :comment, :default => ''
51
+ t.string :action, :default => ''
52
+ t.datetime :created_at
53
+ end
54
+
55
+ end
56
+
57
+ class User < ActiveRecord::Base
58
+ validates_presence_of :login
59
+ validate :presence_of_password
60
+ attr_accessible :login, :password, :birthday
61
+ attr_accessor :password_confirmation
62
+
63
+ def presence_of_password
64
+ if password.blank?
65
+ attribute_name = I18n.t(:password, {:default => "Password", :scope => [:activerecord, :attributes, :user]})
66
+ error_message = I18n.t(:blank, {:default => "can't be blank", :scope => [:activerecord, :errors, :messages]})
67
+ errors.add_to_base("#{attribute_name} #{error_message}")
68
+ end
69
+ end
70
+ end
71
+ class Account < ActiveRecord::Base; end
72
+ class History < ActiveRecord::Base; end
73
+ class Address < ActiveRecord::Base; end
74
+ class AccountInfo < ActiveRecord::Base; end
75
+
76
+ class PresenterWithTwoAddresses < ActivePresenter::Base
77
+ presents :address, :secondary_address => Address
78
+ end
79
+
80
+ class SignupPresenter < ActivePresenter::Base
81
+ presents :account, :user
82
+ attr_protected :account_secret
83
+ end
84
+
85
+ class EndingWithSPresenter < ActivePresenter::Base
86
+ presents :address
87
+ end
88
+
89
+ class HistoricalPresenter < ActivePresenter::Base
90
+ presents :user, :history
91
+ attr_accessible :history_comment
92
+ end
93
+
94
+ class CantSavePresenter < ActivePresenter::Base
95
+ presents :address
96
+
97
+ before_save :halt
98
+
99
+ def halt; false; end
100
+ end
101
+
102
+ class SignupNoAccountPresenter < ActivePresenter::Base
103
+ presents :account, :user
104
+
105
+ def save?(key, instance)
106
+ key != :account
107
+ end
108
+ end
109
+
110
+ class AfterSavePresenter < ActivePresenter::Base
111
+ presents :address
112
+
113
+ after_save :set_street
114
+
115
+ def set_street
116
+ address.street = 'Some Street'
117
+ end
118
+ end
119
+
120
+ class SamePrefixPresenter < ActivePresenter::Base
121
+ presents :account, :account_info
122
+ end
123
+
124
+ class CallbackOrderingPresenter < ActivePresenter::Base
125
+ presents :account
126
+
127
+ before_validation :do_before_validation
128
+ before_save :do_before_save
129
+ after_save :do_after_save
130
+
131
+ attr_reader :steps
132
+
133
+ def initialize(params={})
134
+ super
135
+ @steps = []
136
+ end
137
+
138
+ def do_before_validation
139
+ @steps << :before_validation
140
+ end
141
+
142
+ def do_before_save
143
+ @steps << :before_save
144
+ end
145
+
146
+ def do_after_save
147
+ @steps << :after_save
148
+ end
149
+ end
150
+
151
+ class CallbackCantSavePresenter < ActivePresenter::Base
152
+ presents :account
153
+
154
+ before_validation :do_before_validation
155
+ before_save :do_before_save
156
+ before_save :halt
157
+ after_save :do_after_save
158
+
159
+ attr_reader :steps
160
+
161
+ def initialize(params={})
162
+ super
163
+ @steps = []
164
+ end
165
+
166
+ def do_before_validation
167
+ @steps << :before_validation
168
+ end
169
+
170
+ def do_before_save
171
+ @steps << :before_save
172
+ end
173
+
174
+ def do_after_save
175
+ @steps << :after_save
176
+ end
177
+
178
+ def halt
179
+ false
180
+ end
181
+ end
182
+
183
+ class CallbackCantValidatePresenter < ActivePresenter::Base
184
+ presents :account
185
+
186
+ before_validation :do_before_validation
187
+ before_validation :halt
188
+ before_save :do_before_save
189
+ after_save :do_after_save
190
+
191
+ attr_reader :steps
192
+
193
+ def initialize(params={})
194
+ super
195
+ @steps = []
196
+ end
197
+
198
+ def do_before_validation
199
+ @steps << :before_validation
200
+ end
201
+
202
+ def do_before_save
203
+ @steps << :before_save
204
+ end
205
+
206
+ def do_after_save
207
+ @steps << :after_save
208
+ end
209
+
210
+ def halt
211
+ false
212
+ end
213
+ end
214
+
215
+ def hash_for_user(opts = {})
216
+ {:login => 'jane', :password => 'seekrit' }.merge(opts)
217
+ end
218
+
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: galetahub-active_presenter
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - James Golick
14
+ - Daniel Haran
15
+ - Igor Galeta
16
+ autorequire:
17
+ bindir: bin
18
+ cert_chain: []
19
+
20
+ date: 2011-10-20 00:00:00 Z
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: activerecord
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 3
31
+ segments:
32
+ - 0
33
+ version: "0"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ description: ActivePresenter is the presenter library you already know! (...if you know ActiveRecord)
37
+ email: galeta.igor@gmail.com
38
+ executables: []
39
+
40
+ extensions: []
41
+
42
+ extra_rdoc_files:
43
+ - README.rdoc
44
+ files:
45
+ - lib/active_presenter/base.rb
46
+ - lib/active_presenter/version.rb
47
+ - lib/active_presenter.rb
48
+ - Rakefile
49
+ - README.rdoc
50
+ - test/base_test.rb
51
+ - test/test_helper.rb
52
+ homepage: https://github.com/galetahub/active_presenter
53
+ licenses: []
54
+
55
+ post_install_message:
56
+ rdoc_options: []
57
+
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ hash: 3
66
+ segments:
67
+ - 0
68
+ version: "0"
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ hash: 3
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ requirements: []
79
+
80
+ rubyforge_project: active_presenter
81
+ rubygems_version: 1.8.11
82
+ signing_key:
83
+ specification_version: 3
84
+ summary: ActivePresenter is the presenter library
85
+ test_files:
86
+ - test/base_test.rb
87
+ - test/test_helper.rb