galetahub-active_presenter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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