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.
- data/README.rdoc +86 -0
- data/Rakefile +24 -0
- data/lib/active_presenter.rb +7 -0
- data/lib/active_presenter/base.rb +282 -0
- data/lib/active_presenter/version.rb +4 -0
- data/test/base_test.rb +362 -0
- data/test/test_helper.rb +218 -0
- metadata +87 -0
data/README.rdoc
ADDED
|
@@ -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
|
+
|
data/Rakefile
ADDED
|
@@ -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,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
|
data/test/base_test.rb
ADDED
|
@@ -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
|
data/test/test_helper.rb
ADDED
|
@@ -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
|