inherited_resources 0.9.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.
Files changed (37) hide show
  1. data/CHANGELOG +103 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +524 -0
  4. data/Rakefile +40 -0
  5. data/lib/inherited_resources.rb +23 -0
  6. data/lib/inherited_resources/actions.rb +79 -0
  7. data/lib/inherited_resources/base.rb +42 -0
  8. data/lib/inherited_resources/base_helpers.rb +363 -0
  9. data/lib/inherited_resources/belongs_to_helpers.rb +89 -0
  10. data/lib/inherited_resources/class_methods.rb +338 -0
  11. data/lib/inherited_resources/dsl.rb +26 -0
  12. data/lib/inherited_resources/dumb_responder.rb +20 -0
  13. data/lib/inherited_resources/has_scope_helpers.rb +83 -0
  14. data/lib/inherited_resources/legacy/respond_to.rb +156 -0
  15. data/lib/inherited_resources/legacy/responder.rb +200 -0
  16. data/lib/inherited_resources/polymorphic_helpers.rb +155 -0
  17. data/lib/inherited_resources/singleton_helpers.rb +95 -0
  18. data/lib/inherited_resources/url_helpers.rb +179 -0
  19. data/test/aliases_test.rb +139 -0
  20. data/test/association_chain_test.rb +125 -0
  21. data/test/base_test.rb +225 -0
  22. data/test/belongs_to_test.rb +87 -0
  23. data/test/class_methods_test.rb +138 -0
  24. data/test/customized_base_test.rb +162 -0
  25. data/test/customized_belongs_to_test.rb +76 -0
  26. data/test/defaults_test.rb +70 -0
  27. data/test/flash_test.rb +88 -0
  28. data/test/has_scope_test.rb +139 -0
  29. data/test/nested_belongs_to_test.rb +108 -0
  30. data/test/optional_belongs_to_test.rb +164 -0
  31. data/test/polymorphic_test.rb +186 -0
  32. data/test/redirect_to_test.rb +51 -0
  33. data/test/respond_to_test.rb +155 -0
  34. data/test/singleton_test.rb +83 -0
  35. data/test/test_helper.rb +38 -0
  36. data/test/url_helpers_test.rb +537 -0
  37. metadata +89 -0
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'rake'
4
+ require 'rake/testtask'
5
+ require 'rake/rdoctask'
6
+
7
+ begin
8
+ require 'jeweler'
9
+ Jeweler::Tasks.new do |s|
10
+ s.name = "inherited_resources"
11
+ s.version = "0.9.2"
12
+ s.rubyforge_project = "inherited_resources"
13
+ s.summary = "Inherited Resources speeds up development by making your controllers inherit all restful actions so you just have to focus on what is important."
14
+ s.email = "jose.valim@gmail.com"
15
+ s.homepage = "http://github.com/josevalim/inherited_resources"
16
+ s.description = "Inherited Resources speeds up development by making your controllers inherit all restful actions so you just have to focus on what is important."
17
+ s.authors = ['José Valim']
18
+ s.files = FileList["[A-Z]*", "{lib}/**/*"]
19
+ end
20
+
21
+ Jeweler::GemcutterTasks.new
22
+ rescue LoadError
23
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
24
+ end
25
+
26
+ desc 'Run tests for InheritedResources.'
27
+ Rake::TestTask.new(:test) do |t|
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = true
30
+ end
31
+
32
+ desc 'Generate documentation for InheritedResources.'
33
+ Rake::RDocTask.new(:rdoc) do |rdoc|
34
+ rdoc.rdoc_dir = 'rdoc'
35
+ rdoc.title = 'InheritedResources'
36
+ rdoc.options << '--line-numbers' << '--inline-source'
37
+ rdoc.rdoc_files.include('README.rdoc')
38
+ rdoc.rdoc_files.include('MIT-LICENSE')
39
+ rdoc.rdoc_files.include('lib/**/*.rb')
40
+ end
@@ -0,0 +1,23 @@
1
+ # respond_to is the only file that should be loaded before hand. All others
2
+ # are loaded on demand.
3
+ #
4
+ unless defined?(ActionController::Responder)
5
+ require File.join(File.dirname(__FILE__), 'inherited_resources', 'legacy', 'responder')
6
+ require File.join(File.dirname(__FILE__), 'inherited_resources', 'legacy', 'respond_to')
7
+ end
8
+
9
+ module InheritedResources
10
+ ACTIONS = [ :index, :show, :new, :edit, :create, :update, :destroy ] unless self.const_defined?(:ACTIONS)
11
+ end
12
+
13
+ class ActionController::Base
14
+ # If you cannot inherit from InheritedResources::Base you can call
15
+ # inherit_resource in your controller to have all the required modules and
16
+ # funcionality included.
17
+ #
18
+ def self.inherit_resources
19
+ InheritedResources::Base.inherit_resources(self)
20
+ initialize_resources_class_accessors!
21
+ create_resources_url_helpers!
22
+ end
23
+ end
@@ -0,0 +1,79 @@
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 create_resource(object)
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 update_resource(object, 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
+
63
+ if destroy_resource(object)
64
+ set_flash_message!(:notice, '{{resource_name}} was successfully destroyed.')
65
+ else
66
+ set_flash_message!(:error, '{{resource_name}} could not be destroyed.')
67
+ end
68
+
69
+ options[:location] ||= collection_url rescue nil
70
+ respond_with_dual_blocks(object, options, nil, block)
71
+ end
72
+ alias :destroy! :destroy
73
+
74
+ # Make aliases protected
75
+ protected :index!, :show!, :new!, :create!, :edit!, :update!, :destroy!
76
+
77
+ end
78
+ end
79
+
@@ -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
+ :parent_url, :parent_path, :resource, :collection, :resource_class, :association_chain
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,363 @@
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
+ # Responsible for saving the resource on :create method. Overwriting this
53
+ # allow you to control the way resource is saved. Let's say you have a
54
+ # PassworsController who is responsible for finding an user by email and
55
+ # sent password instructions for him. Instead of overwriting the entire
56
+ # :create method, you could do something:
57
+ #
58
+ # def create_resource(object)
59
+ # object.send_instructions_by_email
60
+ # end
61
+ #
62
+ def create_resource(object)
63
+ object.save
64
+ end
65
+
66
+ # Responsible for updating the resource in :update method. This allow you
67
+ # to handle how the resource is gona be updated, let's say in a different
68
+ # way then the usual :update_attributes:
69
+ #
70
+ # def update_resource(object, attributes)
71
+ # object.reset_password!(attributes)
72
+ # end
73
+ #
74
+ def update_resource(object, attributes)
75
+ object.update_attributes(attributes)
76
+ end
77
+
78
+ # Handle the :destroy method for the resource. Overwrite it to call your
79
+ # own method for destroing the resource, as:
80
+ #
81
+ # def destroy_resource(object)
82
+ # object.cancel
83
+ # end
84
+ #
85
+ def destroy_resource(object)
86
+ object.destroy
87
+ end
88
+
89
+ # This class allows you to set a instance variable to begin your
90
+ # association chain. For example, usually your projects belongs to users
91
+ # and that means that they belong to the current logged in user. So you
92
+ # could do this:
93
+ #
94
+ # def begin_of_association_chain
95
+ # @current_user
96
+ # end
97
+ #
98
+ # So every time we instantiate a project, we will do:
99
+ #
100
+ # @current_user.projects.build(params[:project])
101
+ # @current_user.projects.find(params[:id])
102
+ #
103
+ # The variable set in begin_of_association_chain is not sent when building
104
+ # urls, so this is never going to happen when calling resource_url:
105
+ #
106
+ # project_url(@current_user, @project)
107
+ #
108
+ # If the user actually scopes the url, you should use belongs_to method
109
+ # and declare that projects belong to user.
110
+ #
111
+ def begin_of_association_chain
112
+ nil
113
+ end
114
+
115
+ # Returns if the controller has a parent. When only base helpers are loaded,
116
+ # it's always false and should not be overwriten.
117
+ #
118
+ def parent?
119
+ false
120
+ end
121
+
122
+ # Returns the association chain, with all parents (does not include the
123
+ # current resource).
124
+ #
125
+ def association_chain
126
+ @association_chain ||=
127
+ symbols_for_association_chain.inject([begin_of_association_chain]) do |chain, symbol|
128
+ chain << evaluate_parent(symbol, resources_configuration[symbol], chain.last)
129
+ end.compact.freeze
130
+ end
131
+
132
+ # Overwrite this method to provide other interpolation options when
133
+ # the flash message is going to be set.
134
+ #
135
+ # def interpolation_options
136
+ # { }
137
+ # end
138
+
139
+ private
140
+
141
+ # Fast accessor to resource_collection_name
142
+ #
143
+ def resource_collection_name #:nodoc:
144
+ self.resources_configuration[:self][:collection_name]
145
+ end
146
+
147
+ # Fast accessor to resource_instance_name
148
+ #
149
+ def resource_instance_name #:nodoc:
150
+ self.resources_configuration[:self][:instance_name]
151
+ end
152
+
153
+ # This methods gets your begin_of_association_chain, join it with your
154
+ # parents chain and returns the scoped association.
155
+ #
156
+ def end_of_association_chain #:nodoc:
157
+ if chain = association_chain.last
158
+ if method_for_association_chain
159
+ apply_scope_to(chain.send(method_for_association_chain))
160
+ else
161
+ # This only happens when we specify begin_of_association_chain in
162
+ # a singletion controller without parents. In this case, the chain
163
+ # is exactly the begin_of_association_chain which is already an
164
+ # instance and then not scopable.
165
+ chain
166
+ end
167
+ else
168
+ apply_scope_to(resource_class)
169
+ end
170
+ end
171
+
172
+ # Returns the appropriated method to build the resource.
173
+ #
174
+ def method_for_build #:nodoc:
175
+ (begin_of_association_chain || parent?) ? method_for_association_build : :new
176
+ end
177
+
178
+ # Returns the name of the method used for build the resource in cases
179
+ # where we have a parent. This is overwritten in singleton scenarios.
180
+ #
181
+ def method_for_association_build
182
+ :build
183
+ end
184
+
185
+ # Returns the name of the method to be called, before returning the end
186
+ # of the association chain. This is overwriten by singleton cases
187
+ # where no method for association chain is called.
188
+ #
189
+ def method_for_association_chain #:nodoc:
190
+ resource_collection_name
191
+ end
192
+
193
+ # Get resource ivar based on the current resource controller.
194
+ #
195
+ def get_resource_ivar #:nodoc:
196
+ instance_variable_get("@#{resource_instance_name}")
197
+ end
198
+
199
+ # Set resource ivar based on the current resource controller.
200
+ #
201
+ def set_resource_ivar(resource) #:nodoc:
202
+ instance_variable_set("@#{resource_instance_name}", resource)
203
+ end
204
+
205
+ # Get collection ivar based on the current resource controller.
206
+ #
207
+ def get_collection_ivar #:nodoc:
208
+ instance_variable_get("@#{resource_collection_name}")
209
+ end
210
+
211
+ # Set collection ivar based on the current resource controller.
212
+ #
213
+ def set_collection_ivar(collection) #:nodoc:
214
+ instance_variable_set("@#{resource_collection_name}", collection)
215
+ end
216
+
217
+ # Helper to set flash messages. It's powered by I18n API.
218
+ # It checks for messages in the following order:
219
+ #
220
+ # flash.controller_name.action_name.status
221
+ # flash.actions.action_name.status
222
+ #
223
+ # If none is available, a default message is set. So, if you have
224
+ # a CarsController, create action, it will check for:
225
+ #
226
+ # flash.cars.create.status
227
+ # flash.actions.create.status
228
+ #
229
+ # The statuses can be :notice (when the object can be created, updated
230
+ # or destroyed with success) or :error (when the objecy cannot be created
231
+ # or updated).
232
+ #
233
+ # Those messages are interpolated by using the resource class human name.
234
+ # This means you can set:
235
+ #
236
+ # flash:
237
+ # actions:
238
+ # create:
239
+ # notice: "Hooray! {{resource_name}} was successfully created!"
240
+ #
241
+ # But sometimes, flash messages are not that simple. Going back
242
+ # to cars example, you might want to say the brand of the car when it's
243
+ # updated. Well, that's easy also:
244
+ #
245
+ # flash:
246
+ # cars:
247
+ # update:
248
+ # notice: "Hooray! You just tuned your {{car_brand}}!"
249
+ #
250
+ # Since :car_name is not available for interpolation by default, you have
251
+ # to overwrite interpolation_options.
252
+ #
253
+ # def interpolation_options
254
+ # { :car_brand => @car.brand }
255
+ # end
256
+ #
257
+ # Then you will finally have:
258
+ #
259
+ # 'Hooray! You just tuned your Aston Martin!'
260
+ #
261
+ # If your controller is namespaced, for example Admin::CarsController,
262
+ # the messages will be checked in the following order:
263
+ #
264
+ # flash.admin.cars.create.status
265
+ # flash.admin.actions.create.status
266
+ # flash.cars.create.status
267
+ # flash.actions.create.status
268
+ #
269
+ def set_flash_message!(status, default_message=nil)
270
+ return flash[status] = default_message unless defined?(::I18n)
271
+
272
+ resource_name = if resource_class
273
+ if resource_class.respond_to?(:human_name)
274
+ resource_class.human_name
275
+ else
276
+ resource_class.name.underscore.humanize
277
+ end
278
+ else
279
+ "Resource"
280
+ end
281
+
282
+ given_options = if self.respond_to?(:interpolation_options)
283
+ interpolation_options
284
+ else
285
+ {}
286
+ end
287
+
288
+ options = {
289
+ :default => default_message || '',
290
+ :resource_name => resource_name
291
+ }.merge(given_options)
292
+
293
+ defaults = []
294
+ slices = controller_path.split('/')
295
+
296
+ while slices.size > 0
297
+ defaults << :"flash.#{slices.fill(controller_name, -1).join('.')}.#{action_name}.#{status}"
298
+ defaults << :"flash.#{slices.fill(:actions, -1).join('.')}.#{action_name}.#{status}"
299
+ slices.shift
300
+ end
301
+
302
+ options[:default] = defaults.push(options[:default])
303
+ options[:default].flatten!
304
+
305
+ message = ::I18n.t options[:default].shift, options
306
+ flash[status] = message unless message.blank?
307
+ end
308
+
309
+ # Used to allow to specify success and failure within just one block:
310
+ #
311
+ # def create
312
+ # create! do |success, failure|
313
+ # failure.html { redirect_to root_url }
314
+ # end
315
+ # end
316
+ #
317
+ # It also calculates the response url in case a block without arity is
318
+ # given and returns it. Otherwise returns nil.
319
+ #
320
+ def respond_with_dual_blocks(object, options, success, given_block, &block) #:nodoc:
321
+ case given_block.try(:arity)
322
+ when 2
323
+ respond_with(object, options) do |responder|
324
+ dumb_responder = InheritedResources::DumbResponder.new
325
+ if success
326
+ given_block.call(responder, dumb_responder)
327
+ else
328
+ given_block.call(dumb_responder, responder)
329
+ end
330
+ block.try(:call, responder)
331
+ end
332
+ when 1
333
+ if block
334
+ respond_with(object, options) do |responder|
335
+ given_block.call(responder)
336
+ block.call(responder)
337
+ end
338
+ else
339
+ respond_with(object, options, &given_block)
340
+ end
341
+ else
342
+ options[:location] = given_block.call if given_block
343
+ respond_with(object, options, &block)
344
+ end
345
+ end
346
+
347
+ # Hook to apply scopes. By default returns only the target_object given.
348
+ # It's extend by HasScopeHelpers.
349
+ #
350
+ def apply_scope_to(target_object) #:nodoc:
351
+ target_object
352
+ end
353
+
354
+ # Symbols chain in base helpers return nothing. This is later overwriten
355
+ # by belongs_to and can be complex in polymorphic cases.
356
+ #
357
+ def symbols_for_association_chain #:nodoc:
358
+ []
359
+ end
360
+
361
+ end
362
+ end
363
+