prepro 0.0.0 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,3 @@
1
+ module Prepro
2
+ VERSION = "0.0.2"
3
+ end
data/lib/prepro.rb CHANGED
@@ -1,3 +1,5 @@
1
- # Shell for the Prepro gem
2
- class Prepro
3
- end
1
+ require 'prepro/presenter'
2
+ require 'prepro/processor'
3
+
4
+ module Prepro
5
+ end
data/readme.textile ADDED
@@ -0,0 +1,4 @@
1
+ Prepro gives your Rails app presenters and processors to slim down both controllers and models.
2
+
3
+ This is still a work in progress. I use it for production, however the API might change and it
4
+ requires a lot more documentation.
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: 31
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 0
10
- version: 0.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: 2011-12-20 00:00:00 -08:00
18
+ date: 2012-01-25 00:00:00 -08:00
19
19
  default_executable:
20
20
  dependencies: []
21
21
 
22
- description: Presenters and Processors to make Rails apps more SOLID.
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 to make Rails apps more SOLID.
69
+ summary: Presenters and Processors for clean Rails apps.
66
70
  test_files: []
67
71