prepro 0.0.0 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/prepro/presenter.rb +174 -0
- data/lib/prepro/processor.rb +90 -0
- data/lib/prepro/version.rb +3 -0
- data/lib/prepro.rb +5 -3
- data/readme.textile +4 -0
- metadata +10 -6
@@ -0,0 +1,174 @@
|
|
1
|
+
# Abstract class with common features for Presenters. Use this for any objects that will be rendered
|
2
|
+
# in some shape or form. Use it for read only access, in other words, for index, show, new and edit
|
3
|
+
# actions in a rails app.
|
4
|
+
module Prepro
|
5
|
+
class Presenter
|
6
|
+
|
7
|
+
# Prepares collection of model instances or a single model instance for presentation
|
8
|
+
# @param[Integer, String(number), Hash, Model, Array<Model>] id_model_hash_collection id of model,
|
9
|
+
# attributes for new model, existing model or collection of models to present.
|
10
|
+
# @param[User, AnonymousUser] actor the actor who will view the model
|
11
|
+
# @param[ActionView::Base] view_context An instance of a view class. The default view class is
|
12
|
+
# ActionView::Base
|
13
|
+
# @param[Hash, optional] options
|
14
|
+
# @return[DecoratedModel, Array<DecoratedModel>] a model or collection thereof, decorated for
|
15
|
+
# presentation
|
16
|
+
def self.new(id_model_hash_collection, actor, view_context, options = {})
|
17
|
+
case id_model_hash_collection
|
18
|
+
when Array, ActiveRecord::Relation
|
19
|
+
present_collection(id_model_hash_collection, actor, view_context, options)
|
20
|
+
else
|
21
|
+
present_single(id_model_hash_collection, actor, view_context, options)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Alias the basic access methods, so that they can be called for classes further down the
|
26
|
+
# inheritance chain, after another class overrode the method
|
27
|
+
# Aliasing class methods can only be done in the singleton method
|
28
|
+
# See: http://athikunte.blogspot.com/2008/03/aliasmethod-for-class-methods.html
|
29
|
+
class << self
|
30
|
+
alias_method :new_original, :new
|
31
|
+
end
|
32
|
+
|
33
|
+
# Prepares collection of model instances for presentation
|
34
|
+
# @param[Array<Model>] model_instances A collection of model instances
|
35
|
+
# @param[User, AnonymousUser] actor the actor who will view the model
|
36
|
+
# @param[ActionView::Base] view_context An instance of a view class. The default view class is
|
37
|
+
# ActionView::Base
|
38
|
+
# @param[Hash, optional] options
|
39
|
+
# @return[Array<DecoratedModel>] An array of models, each decorated for presentation
|
40
|
+
def self.present_collection(model_instances, actor, view_context, options = {})
|
41
|
+
presenter_attrs = OpenStruct.new(:actor => actor, :view_context => view_context, :options => options)
|
42
|
+
enforce_permissions(model_class.listable_by?(actor))
|
43
|
+
model_instances.each { |e| make_presentable!(e, presenter_attrs) }
|
44
|
+
model_instances
|
45
|
+
end
|
46
|
+
|
47
|
+
# Prepares a model instance for presentation
|
48
|
+
# @param[Integer, String(number), Model] id_hash_model id of model, attributes for model, or model
|
49
|
+
# to present
|
50
|
+
# @param[User, AnonymousUser] actor the actor who will view the model
|
51
|
+
# @param[ActionView::Base] view_context An instance of a view class. The default view class is
|
52
|
+
# ActionView::Base
|
53
|
+
# @param[Hash, optional] options
|
54
|
+
# @return[DecoratedModel] a model, decorated for presentation
|
55
|
+
def self.present_single(id_hash_model, actor, view_context, options = {})
|
56
|
+
presenter_attrs = OpenStruct.new(:actor => actor, :view_context => view_context, :options => options)
|
57
|
+
model_instance = load_model_instance(id_hash_model)
|
58
|
+
enforce_permissions(model_instance.viewable_by?(actor))
|
59
|
+
make_presentable!(model_instance, presenter_attrs)
|
60
|
+
model_instance
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns a model_instance, based on given id_hash_model
|
64
|
+
def self.load_model_instance(id_hash_model)
|
65
|
+
case id_hash_model
|
66
|
+
when Integer, /\A\d+/
|
67
|
+
model_class.find(id_hash_model)
|
68
|
+
when Hash
|
69
|
+
model_class.new(id_hash_model)
|
70
|
+
else
|
71
|
+
id_hash_model
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Override this in your concrete presenters with the class presented by self.
|
76
|
+
def self.model_class
|
77
|
+
raise "Implement me in concrete presenter"
|
78
|
+
end
|
79
|
+
|
80
|
+
# Override this in your concrete presenters with your own permission handling code.
|
81
|
+
def self.enforce_permissions(has_permission)
|
82
|
+
raise "Implement me in concrete presenter"
|
83
|
+
end
|
84
|
+
|
85
|
+
module DecoratorMixin
|
86
|
+
|
87
|
+
def presenter_attrs=(the_presenter_attrs)
|
88
|
+
@presenter_attrs = the_presenter_attrs
|
89
|
+
end
|
90
|
+
|
91
|
+
def presenter_attrs
|
92
|
+
@presenter_attrs
|
93
|
+
end
|
94
|
+
|
95
|
+
# Formats a_datetime
|
96
|
+
# @param[DateTime, Nil] a_datetime the datetime to format
|
97
|
+
# @param[Symbol] output_format the format to be applied: :distance_in_words,
|
98
|
+
# or any datetime format specified in initializers
|
99
|
+
def formatted_datetime(a_datetime, output_format, options = {})
|
100
|
+
return 'N/A' if a_datetime.blank?
|
101
|
+
case output_format
|
102
|
+
when :distance_in_words
|
103
|
+
if a_datetime < Time.now
|
104
|
+
# in the past
|
105
|
+
decorated_time_ago_in_words(a_datetime, options)
|
106
|
+
else
|
107
|
+
# in the future
|
108
|
+
decorated_time_from_now_in_words(a_datetime, options)
|
109
|
+
end
|
110
|
+
else
|
111
|
+
a_datetime.to_s(output_format)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Renders time ago, showing absolute time on hover
|
116
|
+
# @param[DateTime] a_datetime the time to render
|
117
|
+
# @param[Hash, optional] options:
|
118
|
+
# * :suffix => printed after the time, default: ' ago'
|
119
|
+
# * :suppress_1 => if the number is a one, suppress it. Used for '... in the last month', which
|
120
|
+
# reads better than '... in the last 1 month'
|
121
|
+
# * :text_only => skip html tags
|
122
|
+
def decorated_time_ago_in_words(a_datetime, options = {})
|
123
|
+
options = {
|
124
|
+
:suffix => ' ago',
|
125
|
+
:suppress_1 => false,
|
126
|
+
:text_only => false
|
127
|
+
}.merge(options)
|
128
|
+
ts = ((presenter_attrs.view_context.time_ago_in_words(a_datetime).gsub('about ', '') + options[:suffix]) rescue 'N/A')
|
129
|
+
ts = ts.gsub(/^1\s+/, '') if options[:suppress_1]
|
130
|
+
if options[:text_only]
|
131
|
+
ts
|
132
|
+
else
|
133
|
+
presenter_attrs.view_context.content_tag(:span, ts, :title => a_datetime.to_s(:full_date_and_time))
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Renders time from now, showing absolute time on hover
|
138
|
+
# @param[DateTime] a_datetime the time to render
|
139
|
+
# @param[Hash, optional] options:
|
140
|
+
# * :prefix => printed before the time, default: 'in '
|
141
|
+
# * :suppress_1 => if the number is a one, suppress it. Used for '... in the last month', which
|
142
|
+
# reads better than '... in the last 1 month'
|
143
|
+
# * :text_only => skip html tags
|
144
|
+
def decorated_time_from_now_in_words(a_datetime, options = {})
|
145
|
+
options = {
|
146
|
+
:prefix => 'in ',
|
147
|
+
:suppress_1 => false,
|
148
|
+
:text_only => false
|
149
|
+
}.merge(options)
|
150
|
+
ts = (
|
151
|
+
(
|
152
|
+
options[:prefix] + presenter_attrs.view_context.time_ago_in_words(a_datetime).gsub('about ', '')
|
153
|
+
) rescue 'N/A'
|
154
|
+
)
|
155
|
+
ts = ts.gsub(/^1\s+/, '') if options[:suppress_1]
|
156
|
+
if options[:text_only]
|
157
|
+
ts
|
158
|
+
else
|
159
|
+
presenter_attrs.view_context.content_tag(:span, ts, :title => a_datetime.to_s(:full_date_and_time))
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def formatted_boolean(a_boolean)
|
164
|
+
a_boolean ? 'Yes' : 'No'
|
165
|
+
end
|
166
|
+
|
167
|
+
def indicate_blank
|
168
|
+
presenter_attrs.view_context.content_tag :span, "None Given", :class => 'label'
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# Abstract class with common features for Processors. Use this for any objects that will receive
|
2
|
+
# attributes and will cause a persisted record to be changed or updated. In other words:
|
3
|
+
# Use it for the create, update, and destroy actions in a rails app.
|
4
|
+
module Prepro
|
5
|
+
class Processor
|
6
|
+
|
7
|
+
# Creates a new model_instance based on model_attrs.
|
8
|
+
# @param[Hash] model_attrs The attributes for the new model_instance. Includes DB columns,
|
9
|
+
# associated objects, nested attributes, etc.
|
10
|
+
# @param[User, AnonymousUser, Nil] actor The actor who creates the model_instance
|
11
|
+
# @return[Array<ModelInstance, Boolean>] A tuple with the newly created model_instance and a
|
12
|
+
# success flag.
|
13
|
+
def self.create(model_attrs, actor, options = {})
|
14
|
+
processor_attrs = OpenStruct.new(:attributes => model_attrs, :actor => actor, :options => options)
|
15
|
+
model_instance = model_class.new
|
16
|
+
enforce_permissions(model_instance.creatable_by?(actor))
|
17
|
+
before_assign_attributes_on_create(model_instance, processor_attrs)
|
18
|
+
# use this (instead of attributes=), once we're on Rails3.1: model_instance.assign_attributes(model_attrs, :as => options[:as])
|
19
|
+
model_instance.attributes = model_attrs
|
20
|
+
before_save_on_create(model_instance, processor_attrs)
|
21
|
+
success = model_instance.save
|
22
|
+
[model_instance, success]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Updates an existing model_instance based on model_attrs.
|
26
|
+
# @param[Hash] model_attrs The attributes for the updated model_instance. Includes DB columns,
|
27
|
+
# the model's id, associated objects, nested attributes, etc.
|
28
|
+
# @param[User, AnonymousUser, Nil] actor The actor who updates the model_instance
|
29
|
+
# @return[Array<ModelInstance, Boolean>] A tuple with the updated model_instance and a success flag.
|
30
|
+
def self.update(model_attrs, actor, options = {})
|
31
|
+
processor_attrs = OpenStruct.new(:attributes => model_attrs, :actor => actor, :options => options)
|
32
|
+
model_instance = model_class.find(model_attrs[:id])
|
33
|
+
enforce_permissions(model_instance.updatable_by?(actor))
|
34
|
+
before_assign_attributes_on_update(model_instance, processor_attrs)
|
35
|
+
# use this (instead of attributes=), once we're on Rails3.1: model_instance.assign_attributes(model_attrs, :as => options[:as])
|
36
|
+
model_instance.attributes = model_attrs
|
37
|
+
before_save_on_update(model_instance, processor_attrs)
|
38
|
+
success = model_instance.save
|
39
|
+
[model_instance, success]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Destroys an existing model_instance based on model_id
|
43
|
+
# @param[Integer, String<Number>] model_id The id of the model_instance to be destroyed
|
44
|
+
# @param[User, AnonymousUser, Nil] actor The actor who updates the model_instance
|
45
|
+
# @return[Array<ModelInstance, Boolean>] A tuple with the destroyed model_instance and a success flag.
|
46
|
+
def self.destroy(model_id, actor, options = {})
|
47
|
+
model_instance = model_class.find(model_id)
|
48
|
+
enforce_permissions(model_instance.destroyable_by?(actor))
|
49
|
+
model_instance.destroy
|
50
|
+
[model_instance, true]
|
51
|
+
end
|
52
|
+
|
53
|
+
# Alias the basic access methods, so that they can be called for classes further down the
|
54
|
+
# inheritance chain, after another class overrode the method
|
55
|
+
# Aliasing class methods can only be done in the singleton method
|
56
|
+
# See: http://athikunte.blogspot.com/2008/03/aliasmethod-for-class-methods.html
|
57
|
+
class << self
|
58
|
+
alias_method :create_original, :create
|
59
|
+
alias_method :update_original, :update
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def self.before_assign_attributes_on_create(model_instance, processor_attrs)
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.before_save_on_create(model_instance, processor_attrs)
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.before_assign_attributes_on_update(model_instance, processor_attrs)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.before_save_on_update(model_instance, processor_attrs)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.model_class
|
77
|
+
raise "Implement me in concrete processor"
|
78
|
+
end
|
79
|
+
|
80
|
+
# Override this in your concrete processors with your own permission handling code.
|
81
|
+
def self.enforce_permissions(has_permission)
|
82
|
+
raise "Implement me in concrete processor"
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.make_processable(model_instance, processor_attrs)
|
86
|
+
# nothing to do here, override in specific processors
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
data/lib/prepro.rb
CHANGED
data/readme.textile
ADDED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prepro
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Jo Hund
|
@@ -15,11 +15,11 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2012-01-25 00:00:00 -08:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
22
|
-
description: Presenters and Processors
|
22
|
+
description: Presenters and Processors for clean Rails apps.
|
23
23
|
email: jhund@clearcove.ca
|
24
24
|
executables: []
|
25
25
|
|
@@ -29,6 +29,10 @@ extra_rdoc_files: []
|
|
29
29
|
|
30
30
|
files:
|
31
31
|
- lib/prepro.rb
|
32
|
+
- lib/prepro/presenter.rb
|
33
|
+
- lib/prepro/processor.rb
|
34
|
+
- lib/prepro/version.rb
|
35
|
+
- readme.textile
|
32
36
|
has_rdoc: true
|
33
37
|
homepage: http://rubygems.org/gems/prepro
|
34
38
|
licenses: []
|
@@ -62,6 +66,6 @@ rubyforge_project:
|
|
62
66
|
rubygems_version: 1.5.2
|
63
67
|
signing_key:
|
64
68
|
specification_version: 3
|
65
|
-
summary: Presenters and Processors
|
69
|
+
summary: Presenters and Processors for clean Rails apps.
|
66
70
|
test_files: []
|
67
71
|
|