prepro 0.0.0 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|