emmanuel-inherited_resources 0.9.1

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.
@@ -0,0 +1,74 @@
1
+ module InheritedResources
2
+ # Holds all default actions for InheritedResouces.
3
+ module Actions
4
+
5
+ # GET /resources
6
+ def index(&block)
7
+ respond_with(collection, &block)
8
+ end
9
+ alias :index! :index
10
+
11
+ # GET /resources/1
12
+ def show(&block)
13
+ respond_with(resource, &block)
14
+ end
15
+ alias :show! :show
16
+
17
+ # GET /resources/new
18
+ def new(&block)
19
+ respond_with(build_resource, &block)
20
+ end
21
+ alias :new! :new
22
+
23
+ # GET /resources/1/edit
24
+ def edit(&block)
25
+ respond_with(resource, &block)
26
+ end
27
+ alias :edit! :edit
28
+
29
+ # POST /resources
30
+ def create(options={}, &block)
31
+ object = build_resource
32
+
33
+ if object.save
34
+ set_flash_message!(:notice, '{{resource_name}} was successfully created.')
35
+ options[:location] ||= resource_url rescue nil
36
+ respond_with_dual_blocks(object, options, true, block)
37
+ else
38
+ set_flash_message!(:error)
39
+ respond_with_dual_blocks(object, options, false, block)
40
+ end
41
+ end
42
+ alias :create! :create
43
+
44
+ # PUT /resources/1
45
+ def update(options={}, &block)
46
+ object = resource
47
+
48
+ if object.update_attributes(params[resource_instance_name])
49
+ set_flash_message!(:notice, '{{resource_name}} was successfully updated.')
50
+ options[:location] ||= resource_url rescue nil
51
+ respond_with_dual_blocks(object, options, true, block)
52
+ else
53
+ set_flash_message!(:error)
54
+ respond_with_dual_blocks(object, options, false, block)
55
+ end
56
+ end
57
+ alias :update! :update
58
+
59
+ # DELETE /resources/1
60
+ def destroy(options={}, &block)
61
+ object = resource
62
+ object.destroy
63
+
64
+ set_flash_message!(:notice, '{{resource_name}} was successfully destroyed.')
65
+ options[:location] ||= collection_url rescue nil
66
+ respond_with_dual_blocks(object, options, nil, block)
67
+ end
68
+ alias :destroy! :destroy
69
+
70
+ # Make aliases protected
71
+ protected :index!, :show!, :new!, :create!, :edit!, :update!, :destroy!
72
+
73
+ end
74
+ end
@@ -0,0 +1,42 @@
1
+ module InheritedResources
2
+ # = Base
3
+ #
4
+ # This is the base class that holds all actions. If you see the code for each
5
+ # action, they are quite similar to Rails default scaffold.
6
+ #
7
+ # To change your base behavior, you can overwrite your actions and call super,
8
+ # call <tt>default</tt> class method, call <<tt>actions</tt> class method
9
+ # or overwrite some helpers in the base_helpers.rb file.
10
+ #
11
+ class Base < ::ApplicationController
12
+ unloadable
13
+
14
+ # Overwrite inherit_resources to add specific InheritedResources behavior.
15
+ #
16
+ def self.inherit_resources(base)
17
+ base.class_eval do
18
+ include InheritedResources::Actions
19
+ include InheritedResources::BaseHelpers
20
+ extend InheritedResources::ClassMethods
21
+ extend InheritedResources::UrlHelpers
22
+
23
+ # Add at least :html mime type
24
+ respond_to :html
25
+
26
+ helper_method :collection_url, :collection_path, :resource_url, :resource_path,
27
+ :new_resource_url, :new_resource_path, :edit_resource_url, :edit_resource_path,
28
+ :resource, :collection, :resource_class
29
+
30
+ base.with_options :instance_writer => false do |c|
31
+ c.class_inheritable_accessor :resource_class
32
+ c.class_inheritable_array :parents_symbols
33
+ c.class_inheritable_hash :resources_configuration, :scopes_configuration
34
+ end
35
+
36
+ protected :resource_class, :parents_symbols, :resources_configuration, :scopes_configuration
37
+ end
38
+ end
39
+
40
+ inherit_resources(self)
41
+ end
42
+ end
@@ -0,0 +1,333 @@
1
+ # Whenever base is required load the dumb responder since it's used inside actions.
2
+ require File.dirname(__FILE__) + '/dumb_responder.rb'
3
+
4
+ module InheritedResources
5
+ # Base helpers for InheritedResource work. Some methods here can be overwriten
6
+ # and you will need to do that to customize your controllers from time to time.
7
+ #
8
+ module BaseHelpers
9
+
10
+ protected
11
+
12
+ # This is how the collection is loaded.
13
+ #
14
+ # You might want to overwrite this method if you want to add pagination
15
+ # for example. When you do that, don't forget to cache the result in an
16
+ # instance_variable:
17
+ #
18
+ # def collection
19
+ # @projects ||= end_of_association_chain.paginate(params[:page]).all
20
+ # end
21
+ #
22
+ def collection
23
+ get_collection_ivar || set_collection_ivar(end_of_association_chain.find(:all))
24
+ end
25
+
26
+ # This is how the resource is loaded.
27
+ #
28
+ # You might want to overwrite this method when you are using permalink.
29
+ # When you do that, don't forget to cache the result in an
30
+ # instance_variable:
31
+ #
32
+ # def resource
33
+ # @project ||= end_of_association_chain.find_by_permalink!(params[:id])
34
+ # end
35
+ #
36
+ # You also might want to add the exclamation mark at the end of the method
37
+ # because it will raise a 404 if nothing can be found. Otherwise it will
38
+ # probably render a 500 error message.
39
+ #
40
+ def resource
41
+ get_resource_ivar || set_resource_ivar(end_of_association_chain.find(params[:id]))
42
+ end
43
+
44
+ # This method is responsable for building the object on :new and :create
45
+ # methods. If you overwrite it, don't forget to cache the result in an
46
+ # instance variable.
47
+ #
48
+ def build_resource
49
+ get_resource_ivar || set_resource_ivar(end_of_association_chain.send(method_for_build, params[resource_instance_name] || {}))
50
+ end
51
+
52
+ # This class allows you to set a instance variable to begin your
53
+ # association chain. For example, usually your projects belongs to users
54
+ # and that means that they belong to the current logged in user. So you
55
+ # could do this:
56
+ #
57
+ # def begin_of_association_chain
58
+ # @current_user
59
+ # end
60
+ #
61
+ # So every time we instantiate a project, we will do:
62
+ #
63
+ # @current_user.projects.build(params[:project])
64
+ # @current_user.projects.find(params[:id])
65
+ #
66
+ # The variable set in begin_of_association_chain is not sent when building
67
+ # urls, so this is never going to happen when calling resource_url:
68
+ #
69
+ # project_url(@current_user, @project)
70
+ #
71
+ # If the user actually scopes the url, you should use belongs_to method
72
+ # and declare that projects belong to user.
73
+ #
74
+ def begin_of_association_chain
75
+ nil
76
+ end
77
+
78
+ # Returns if the controller has a parent. When only base helpers are loaded,
79
+ # it's always false and should not be overwriten.
80
+ #
81
+ def parent?
82
+ false
83
+ end
84
+
85
+ # Overwrite this method to provide other interpolation options when
86
+ # the flash message is going to be set.
87
+ #
88
+ # def interpolation_options
89
+ # { }
90
+ # end
91
+
92
+ private
93
+
94
+ # Fast accessor to resource_collection_name
95
+ #
96
+ def resource_collection_name #:nodoc:
97
+ self.resources_configuration[:self][:collection_name]
98
+ end
99
+
100
+ # Fast accessor to resource_instance_name
101
+ #
102
+ def resource_instance_name #:nodoc:
103
+ self.resources_configuration[:self][:instance_name]
104
+ end
105
+
106
+ def context_chain
107
+ @context_chain ||= begin
108
+ chain = []
109
+ end_of_chain = symbols_for_association_chain.inject(begin_of_association_chain) do |previous, symbol|
110
+ chain << previous if previous
111
+ evaluate_parent(symbol, resources_configuration[symbol], previous)
112
+ end
113
+ chain << end_of_chain if end_of_chain
114
+
115
+ chain
116
+ end
117
+ end
118
+
119
+ def association_chain
120
+ @association_chain ||= context_chain + (params[:id] ? [resource] : [])
121
+ end
122
+
123
+ # This methods gets your begin_of_association_chain, join it with your
124
+ # parents chain and returns the scoped association.
125
+ #
126
+ def end_of_association_chain #:nodoc:
127
+ chain = context_chain.last
128
+ if chain
129
+ if method_for_association_chain
130
+ apply_scope_to(chain.send(method_for_association_chain))
131
+ else
132
+ # This only happens when we specify begin_of_association_chain in
133
+ # a singletion controller without parents. In this case, the chain
134
+ # is exactly the begin_of_association_chain which is already an
135
+ # instance and then not scopable.
136
+ chain
137
+ end
138
+ else
139
+ apply_scope_to(resource_class)
140
+ end
141
+ end
142
+
143
+ # Returns the appropriated method to build the resource.
144
+ #
145
+ def method_for_build #:nodoc:
146
+ (begin_of_association_chain || parent?) ? method_for_association_build : :new
147
+ end
148
+
149
+ # Returns the name of the method used for build the resource in cases
150
+ # where we have a parent. This is overwritten in singleton scenarios.
151
+ #
152
+ def method_for_association_build
153
+ :build
154
+ end
155
+
156
+ # Returns the name of the method to be called, before returning the end
157
+ # of the association chain. This is overwriten by singleton cases
158
+ # where no method for association chain is called.
159
+ #
160
+ def method_for_association_chain #:nodoc:
161
+ resource_collection_name
162
+ end
163
+
164
+ # Get resource ivar based on the current resource controller.
165
+ #
166
+ def get_resource_ivar #:nodoc:
167
+ instance_variable_get("@#{resource_instance_name}")
168
+ end
169
+
170
+ # Set resource ivar based on the current resource controller.
171
+ #
172
+ def set_resource_ivar(resource) #:nodoc:
173
+ instance_variable_set("@#{resource_instance_name}", resource)
174
+ end
175
+
176
+ # Get collection ivar based on the current resource controller.
177
+ #
178
+ def get_collection_ivar #:nodoc:
179
+ instance_variable_get("@#{resource_collection_name}")
180
+ end
181
+
182
+ # Set collection ivar based on the current resource controller.
183
+ #
184
+ def set_collection_ivar(collection) #:nodoc:
185
+ instance_variable_set("@#{resource_collection_name}", collection)
186
+ end
187
+
188
+ # Helper to set flash messages. It's powered by I18n API.
189
+ # It checks for messages in the following order:
190
+ #
191
+ # flash.controller_name.action_name.status
192
+ # flash.actions.action_name.status
193
+ #
194
+ # If none is available, a default message is set. So, if you have
195
+ # a CarsController, create action, it will check for:
196
+ #
197
+ # flash.cars.create.status
198
+ # flash.actions.create.status
199
+ #
200
+ # The statuses can be :notice (when the object can be created, updated
201
+ # or destroyed with success) or :error (when the objecy cannot be created
202
+ # or updated).
203
+ #
204
+ # Those messages are interpolated by using the resource class human name.
205
+ # This means you can set:
206
+ #
207
+ # flash:
208
+ # actions:
209
+ # create:
210
+ # notice: "Hooray! {{resource_name}} was successfully created!"
211
+ #
212
+ # But sometimes, flash messages are not that simple. Going back
213
+ # to cars example, you might want to say the brand of the car when it's
214
+ # updated. Well, that's easy also:
215
+ #
216
+ # flash:
217
+ # cars:
218
+ # update:
219
+ # notice: "Hooray! You just tuned your {{car_brand}}!"
220
+ #
221
+ # Since :car_name is not available for interpolation by default, you have
222
+ # to overwrite interpolation_options.
223
+ #
224
+ # def interpolation_options
225
+ # { :car_brand => @car.brand }
226
+ # end
227
+ #
228
+ # Then you will finally have:
229
+ #
230
+ # 'Hooray! You just tuned your Aston Martin!'
231
+ #
232
+ # If your controller is namespaced, for example Admin::CarsController,
233
+ # the messages will be checked in the following order:
234
+ #
235
+ # flash.admin.cars.create.status
236
+ # flash.admin.actions.create.status
237
+ # flash.cars.create.status
238
+ # flash.actions.create.status
239
+ #
240
+ def set_flash_message!(status, default_message=nil)
241
+ return flash[status] = default_message unless defined?(::I18n)
242
+
243
+ resource_name = if resource_class
244
+ if resource_class.respond_to?(:human_name)
245
+ resource_class.human_name
246
+ else
247
+ resource_class.name.humanize
248
+ end
249
+ else
250
+ "Resource"
251
+ end
252
+
253
+ given_options = if self.respond_to?(:interpolation_options)
254
+ interpolation_options
255
+ else
256
+ {}
257
+ end
258
+
259
+ options = {
260
+ :default => default_message || '',
261
+ :resource_name => resource_name
262
+ }.merge(given_options)
263
+
264
+ defaults = []
265
+ slices = controller_path.split('/')
266
+
267
+ while slices.size > 0
268
+ defaults << :"flash.#{slices.fill(controller_name, -1).join('.')}.#{action_name}.#{status}"
269
+ defaults << :"flash.#{slices.fill(:actions, -1).join('.')}.#{action_name}.#{status}"
270
+ slices.shift
271
+ end
272
+
273
+ options[:default] = defaults.push(options[:default])
274
+ options[:default].flatten!
275
+
276
+ message = ::I18n.t options[:default].shift, options
277
+ flash[status] = message unless message.blank?
278
+ end
279
+
280
+ # Used to allow to specify success and failure within just one block:
281
+ #
282
+ # def create
283
+ # create! do |success, failure|
284
+ # failure.html { redirect_to root_url }
285
+ # end
286
+ # end
287
+ #
288
+ # It also calculates the response url in case a block without arity is
289
+ # given and returns it. Otherwise returns nil.
290
+ #
291
+ def respond_with_dual_blocks(object, options, success, given_block, &block) #:nodoc:
292
+ case given_block.try(:arity)
293
+ when 2
294
+ respond_with(object, options) do |responder|
295
+ dumb_responder = InheritedResources::DumbResponder.new
296
+ if success
297
+ given_block.call(responder, dumb_responder)
298
+ else
299
+ given_block.call(dumb_responder, responder)
300
+ end
301
+ block.try(:call, responder)
302
+ end
303
+ when 1
304
+ if block
305
+ respond_with(object, options) do |responder|
306
+ given_block.call(responder)
307
+ block.call(responder)
308
+ end
309
+ else
310
+ respond_with(object, options, &given_block)
311
+ end
312
+ else
313
+ options[:location] = given_block.call if given_block
314
+ respond_with(object, options, &block)
315
+ end
316
+ end
317
+
318
+ # Hook to apply scopes. By default returns only the target_object given.
319
+ # It's extend by HasScopeHelpers.
320
+ #
321
+ def apply_scope_to(target_object) #:nodoc:
322
+ target_object
323
+ end
324
+
325
+ # Symbols chain in base helpers return nothing. This is later overwriten
326
+ # by belongs_to and can be complex in polymorphic cases.
327
+ #
328
+ def symbols_for_association_chain #:nodoc:
329
+ []
330
+ end
331
+
332
+ end
333
+ end
@@ -0,0 +1,89 @@
1
+ module InheritedResources
2
+
3
+ # = belongs_to
4
+ #
5
+ # Let's suppose that we have some tasks that belongs to projects. To specify
6
+ # this assoication in your controllers, just do:
7
+ #
8
+ # class TasksController < InheritedResources::Base
9
+ # belongs_to :project
10
+ # end
11
+ #
12
+ # belongs_to accepts several options to be able to configure the association.
13
+ # For example, if you want urls like /projects/:project_title/tasks, you
14
+ # can customize how InheritedResources find your projects:
15
+ #
16
+ # class TasksController < InheritedResources::Base
17
+ # belongs_to :project, :finder => :find_by_title!, :param => :project_title
18
+ # end
19
+ #
20
+ # It also accepts :route_name, :parent_class and :instance_name as options.
21
+ # Check the lib/inherited_resources/class_methods.rb for more.
22
+ #
23
+ # = nested_belongs_to
24
+ #
25
+ # Now, our Tasks get some Comments and you need to nest even deeper. Good
26
+ # practices says that you should never nest more than two resources, but sometimes
27
+ # you have to for security reasons. So this is an example of how you can do it:
28
+ #
29
+ # class CommentsController < InheritedResources::Base
30
+ # nested_belongs_to :project, :task
31
+ # end
32
+ #
33
+ # If you need to configure any of these belongs to, you can nested them using blocks:
34
+ #
35
+ # class CommentsController < InheritedResources::Base
36
+ # belongs_to :project, :finder => :find_by_title!, :param => :project_title do
37
+ # belongs_to :task
38
+ # end
39
+ # end
40
+ #
41
+ # Warning: calling several belongs_to is the same as nesting them:
42
+ #
43
+ # class CommentsController < InheritedResources::Base
44
+ # belongs_to :project
45
+ # belongs_to :task
46
+ # end
47
+ #
48
+ # In other words, the code above is the same as calling nested_belongs_to.
49
+ #
50
+ module BelongsToHelpers
51
+
52
+ protected
53
+
54
+ # Parent is always true when belongs_to is called.
55
+ #
56
+ def parent?
57
+ true
58
+ end
59
+
60
+ private
61
+
62
+ # Evaluate the parent given. This is used to nest parents in the
63
+ # association chain.
64
+ #
65
+ def evaluate_parent(parent_symbol, parent_config, chain = nil) #:nodoc:
66
+ instantiated_object = instance_variable_get("@#{parent_config[:instance_name]}")
67
+ return instantiated_object if instantiated_object
68
+
69
+ parent = if chain
70
+ chain.send(parent_config[:collection_name])
71
+ else
72
+ parent_config[:parent_class]
73
+ end
74
+
75
+ parent = parent.send(parent_config[:finder], params[parent_config[:param]])
76
+
77
+ instance_variable_set("@#{parent_config[:instance_name]}", parent)
78
+ end
79
+
80
+ # Maps parents_symbols to build association chain. In this case, it
81
+ # simply return the parent_symbols, however on polymorphic belongs to,
82
+ # it has some customization.
83
+ #
84
+ def symbols_for_association_chain #:nodoc:
85
+ parents_symbols
86
+ end
87
+
88
+ end
89
+ end