make_resourceful 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/Rakefile +31 -0
  2. data/Readme.rdoc +229 -0
  3. data/VERSION +1 -0
  4. data/lib/make_resourceful.rb +11 -0
  5. data/lib/resourceful/base.rb +63 -0
  6. data/lib/resourceful/builder.rb +405 -0
  7. data/lib/resourceful/default/accessors.rb +418 -0
  8. data/lib/resourceful/default/actions.rb +101 -0
  9. data/lib/resourceful/default/callbacks.rb +51 -0
  10. data/lib/resourceful/default/responses.rb +118 -0
  11. data/lib/resourceful/default/urls.rb +136 -0
  12. data/lib/resourceful/generators/resourceful_scaffold/resourceful_scaffold_generator.rb +87 -0
  13. data/lib/resourceful/generators/resourceful_scaffold/templates/controller.rb +5 -0
  14. data/lib/resourceful/generators/resourceful_scaffold/templates/fixtures.yml +10 -0
  15. data/lib/resourceful/generators/resourceful_scaffold/templates/functional_test.rb +50 -0
  16. data/lib/resourceful/generators/resourceful_scaffold/templates/helper.rb +2 -0
  17. data/lib/resourceful/generators/resourceful_scaffold/templates/migration.rb +13 -0
  18. data/lib/resourceful/generators/resourceful_scaffold/templates/model.rb +2 -0
  19. data/lib/resourceful/generators/resourceful_scaffold/templates/unit_test.rb +7 -0
  20. data/lib/resourceful/generators/resourceful_scaffold/templates/view__form.haml +5 -0
  21. data/lib/resourceful/generators/resourceful_scaffold/templates/view_edit.haml +11 -0
  22. data/lib/resourceful/generators/resourceful_scaffold/templates/view_index.haml +5 -0
  23. data/lib/resourceful/generators/resourceful_scaffold/templates/view_new.haml +9 -0
  24. data/lib/resourceful/generators/resourceful_scaffold/templates/view_partial.haml +12 -0
  25. data/lib/resourceful/generators/resourceful_scaffold/templates/view_show.haml +14 -0
  26. data/lib/resourceful/maker.rb +92 -0
  27. data/lib/resourceful/response.rb +33 -0
  28. data/lib/resourceful/serialize.rb +185 -0
  29. data/spec/accessors_spec.rb +474 -0
  30. data/spec/actions_spec.rb +310 -0
  31. data/spec/base_spec.rb +12 -0
  32. data/spec/builder_spec.rb +332 -0
  33. data/spec/callbacks_spec.rb +71 -0
  34. data/spec/integration_spec.rb +394 -0
  35. data/spec/maker_spec.rb +91 -0
  36. data/spec/response_spec.rb +37 -0
  37. data/spec/responses_spec.rb +314 -0
  38. data/spec/serialize_spec.rb +133 -0
  39. data/spec/urls_spec.rb +282 -0
  40. metadata +97 -0
@@ -0,0 +1,405 @@
1
+ require 'resourceful/response'
2
+ require 'resourceful/serialize'
3
+ require 'resourceful/default/actions'
4
+
5
+ module Resourceful
6
+ # The Maker#make_resourceful block is evaluated in the context
7
+ # of an instance of this class.
8
+ # It provides various methods for customizing the behavior of the actions
9
+ # built by make_resourceful.
10
+ #
11
+ # All instance methods of this class are available in the +make_resourceful+ block.
12
+ class Builder
13
+ # The klass of the controller on which the builder is working.
14
+ attr :controller, true
15
+
16
+ # The constructor is only meant to be called internally.
17
+ #
18
+ # This takes the klass (class object) of a controller
19
+ # and constructs a Builder ready to apply the make_resourceful
20
+ # additions to the controller.
21
+ def initialize(kontroller)
22
+ @controller = kontroller
23
+ @inherited = !kontroller.resourceful_responses.blank?
24
+ @action_module = Resourceful::Default::Actions.dup
25
+ @ok_actions = []
26
+ @callbacks = {:before => {}, :after => {}}
27
+ @responses = {}
28
+ @publish = {}
29
+ @parents = []
30
+ @shallow_parent = nil
31
+ @custom_member_actions = []
32
+ @custom_collection_actions = []
33
+ end
34
+
35
+ # This method is only meant to be called internally.
36
+ #
37
+ # Applies all the changes that have been declared
38
+ # via the instance methods of this Builder
39
+ # to the kontroller passed in to the constructor.
40
+ def apply
41
+ apply_publish
42
+
43
+ kontroller = @controller
44
+
45
+ Resourceful::ACTIONS.each do |action_named|
46
+ # See if this is a method listed by #actions
47
+ unless @ok_actions.include? action_named
48
+ # If its not listed, then remove the method
49
+ # No one can hit it... if its DEAD!
50
+ @action_module.send :remove_method, action_named
51
+ end
52
+ end
53
+
54
+ kontroller.hidden_actions.reject! &@ok_actions.method(:include?)
55
+ kontroller.send :include, @action_module
56
+
57
+ merged_callbacks = kontroller.resourceful_callbacks.merge @callbacks
58
+ merged_responses = kontroller.resourceful_responses.merge @responses
59
+
60
+ kontroller.resourceful_callbacks = merged_callbacks
61
+ kontroller.resourceful_responses = merged_responses
62
+ kontroller.made_resourceful = true
63
+
64
+ kontroller.parents = @parents
65
+ kontroller.shallow_parent = @shallow_parent
66
+ kontroller.model_namespace = @model_namespace
67
+ kontroller.before_filter :load_object, :only => (@ok_actions & SINGULAR_PRELOADED_ACTIONS) + @custom_member_actions
68
+ kontroller.before_filter :load_objects, :only => (@ok_actions & PLURAL_ACTIONS) + @custom_collection_actions
69
+ kontroller.before_filter :load_parent_object, :only => @ok_actions + @custom_member_actions + @custom_collection_actions
70
+ end
71
+
72
+ # :call-seq:
73
+ # actions(*available_actions)
74
+ # actions :all
75
+ #
76
+ # Adds the default RESTful actions to the controller.
77
+ #
78
+ # If the only argument is <tt>:all</tt>,
79
+ # adds all the actions listed in Resourceful::ACTIONS[link:classes/Resourceful.html]
80
+ # (or Resourceful::SINGULAR_ACTIONS[link:classes/Resourceful.html]
81
+ # for a singular controller).
82
+ #
83
+ # Otherwise, this adds all actions
84
+ # whose names were passed as arguments.
85
+ #
86
+ # For example:
87
+ #
88
+ # actions :show, :new, :create
89
+ #
90
+ # This adds the +show+, +new+, and +create+ actions
91
+ # to the controller.
92
+ #
93
+ # The available actions are defined in Default::Actions.
94
+ def actions(*available_actions)
95
+ # FIXME HACK
96
+ # made all methods private, so plural?, too.
97
+ # Did not want to make an exception for that and i do not like it to
98
+ # come up on actions_methods.
99
+ # TODO: maybe we can define plural? as class_method
100
+ if available_actions.first == :all
101
+ if controller.respond_to?(:new_without_capture)
102
+ available_actions = controller.new_without_capture.send(:plural?) ? ACTIONS : SINGULAR_ACTIONS
103
+ else
104
+ available_actions = controller.new.send(:plural?) ? ACTIONS : SINGULAR_ACTIONS
105
+ end
106
+ end
107
+
108
+ available_actions.each { |action| @ok_actions << action.to_sym }
109
+ end
110
+ alias build actions
111
+
112
+ # :call-seq:
113
+ # member_actions(*available_actions)
114
+ #
115
+ # Registers custom member actions which will use the load_object before_filter.
116
+ # These actions are not created, but merely registered for filtering.
117
+ def member_actions(*available_actions)
118
+ available_actions.each { |action| @custom_member_actions << action.to_sym }
119
+ end
120
+
121
+ # :call-seq:
122
+ # collection_actions(*available_actions)
123
+ #
124
+ # Registers custom collection actions which will use the load_objects before_filter.
125
+ # These actions are not created, but merely registered for filtering.
126
+ def collection_actions(*available_actions)
127
+ available_actions.each { |action| @custom_collection_actions << action.to_sym }
128
+ end
129
+
130
+ # :call-seq:
131
+ # before(*events) { ... }
132
+ #
133
+ # Sets up a block of code to run before one or more events.
134
+ #
135
+ # All the default actions can be used as +before+ events:
136
+ # <tt>:index</tt>, <tt>:show</tt>, <tt>:create</tt>, <tt>:update</tt>, <tt>:new</tt>, <tt>:edit</tt>, and <tt>:destroy</tt>.
137
+ #
138
+ # +before+ events are run after any objects are loaded[link:classes/Resourceful/Default/Accessors.html#M000015],
139
+ # but before any database operations or responses.
140
+ #
141
+ # For example:
142
+ #
143
+ # before :show, :edit do
144
+ # @page_title = current_object.title
145
+ # end
146
+ #
147
+ # This will set the <tt>@page_title</tt> variable
148
+ # to the current object's title
149
+ # for the show and edit actions.
150
+ #
151
+ # Successive before blocks for the same action will be chained and executed
152
+ # in order when the event occurs.
153
+ #
154
+ # For example:
155
+ #
156
+ # before :show, :edit do
157
+ # @page_title = current_object.title
158
+ # end
159
+ #
160
+ # before :show do
161
+ # @side_bar = true
162
+ # end
163
+ #
164
+ # These before blocks will both be executed for the show action and in the
165
+ # same order as they were defined.
166
+ def before(*events, &block)
167
+ add_callback :before, *events, &block
168
+ end
169
+
170
+ # :call-seq:
171
+ # after(*events) { ... }
172
+ #
173
+ # Sets up a block of code to run after one or more events.
174
+ #
175
+ # There are two sorts of +after+ events.
176
+ # <tt>:create</tt>, <tt>:update</tt>, and <tt>:destroy</tt>
177
+ # are run after their respective database operations
178
+ # have been completed successfully.
179
+ # <tt>:create_fails</tt>, <tt>:update_fails</tt>, and <tt>:destroy_fails</tt>,
180
+ # on the other hand,
181
+ # are run after the database operations fail.
182
+ #
183
+ # +after+ events are run after the database operations
184
+ # but before any responses.
185
+ #
186
+ # For example:
187
+ #
188
+ # after :create_fails, :update_fails do
189
+ # current_object.password = nil
190
+ # end
191
+ #
192
+ # This will nillify the password of the current object
193
+ # if the object creation/modification failed.
194
+ def after(*events, &block)
195
+ add_callback :after, *events, &block
196
+ end
197
+
198
+ # :call-seq:
199
+ # response_for(*actions) { ... }
200
+ # response_for(*actions) { |format| ... }
201
+ #
202
+ # Sets up a block of code to run
203
+ # instead of the default responses for one or more events.
204
+ #
205
+ # If the block takes a format parameter,
206
+ # it has the same semantics as Rails' +respond_to+ method.
207
+ # Various format methods are called on the format object
208
+ # with blocks that say what to do for each format.
209
+ # For example:
210
+ #
211
+ # response_for :index do |format|
212
+ # format.html
213
+ # format.atom do
214
+ # headers['Content-Type'] = 'application/atom+xml; charset=utf-8'
215
+ # render :action => 'atom', :layout => false
216
+ # end
217
+ # end
218
+ #
219
+ # This doesn't do anything special for the HTML
220
+ # other than ensure that the proper view will be rendered,
221
+ # but for ATOM it sets the proper content type
222
+ # and renders the atom template.
223
+ #
224
+ # If you only need to set the HTML response,
225
+ # you can omit the format parameter.
226
+ # For example:
227
+ #
228
+ # response_for :new do
229
+ # render :action => 'edit'
230
+ # end
231
+ #
232
+ # This is the same as
233
+ #
234
+ # response_for :new do |format|
235
+ # format.html { render :action => 'edit' }
236
+ # end
237
+ #
238
+ # The default responses are defined by
239
+ # Default::Responses.included[link:classes/Resourceful/Default/Responses.html#M000011].
240
+ def response_for(*actions, &block)
241
+ raise "Must specify one or more actions for response_for." if actions.empty?
242
+
243
+ if block.arity < 1
244
+ response_for(*actions) do |format|
245
+ format.html(&block)
246
+ end
247
+ else
248
+ response = Response.new
249
+ block.call response
250
+
251
+ actions.each do |action|
252
+ @responses[action.to_sym] = response.formats
253
+ end
254
+ end
255
+ end
256
+
257
+ # :call-seq:
258
+ # publish *formats, options = {}, :attributes => [ ... ]
259
+ #
260
+ # publish allows you to easily expose information about resourcess in a variety of formats.
261
+ # The +formats+ parameter is a list of formats
262
+ # in which to publish the resources.
263
+ # The formats supported by default are +xml+, +yaml+, and +json+,
264
+ # but other formats may be added by defining +to_format+ methods
265
+ # for the Array and Hash classes
266
+ # and registering the mime type with Rails' Mime::Type.register[http://api.rubyonrails.org/classes/Mime/Type.html#M001115].
267
+ # See Resourceful::Serialize for more details..
268
+ #
269
+ # The <tt>:attributes</tt> option is mandatory.
270
+ # It takes an array of attributes (as symbols) to make public.
271
+ # These attributes can refer to any method on current_object;
272
+ # they aren't limited to database fields.
273
+ # For example:
274
+ #
275
+ # # posts_controller.rb
276
+ # publish :yaml, :attributes => [:title, :created_at, :rendered_content]
277
+ #
278
+ # Then GET /posts/12.yaml would render
279
+ #
280
+ # ---
281
+ # post:
282
+ # title: Cool Stuff
283
+ # rendered_content: |-
284
+ # <p>This is a post.</p>
285
+ # <p>It's about <strong>really</strong> cool stuff.</p>
286
+ # created_at: 2007-04-28 04:32:08 -07:00
287
+ #
288
+ # The <tt>:attributes</tt> array can even contain attributes
289
+ # that are themselves models.
290
+ # In this case, you must use a hash to specify their attributes as well.
291
+ # For example:
292
+ #
293
+ # # person_controller.rb
294
+ # publish :xml, :json, :attributes => [
295
+ # :name, :favorite_color, {
296
+ # :pet_cat => [:name, :breed],
297
+ # :hat => [:type]
298
+ # }]
299
+ #
300
+ # Then GET /people/18.xml would render
301
+ #
302
+ # <?xml version="1.0" encoding="UTF-8"?>
303
+ # <person>
304
+ # <name>Nathan</name>
305
+ # <favorite-color>blue</favorite_color>
306
+ # <pet-cat>
307
+ # <name>Jasmine</name>
308
+ # <breed>panther</breed>
309
+ # </pet-cat>
310
+ # <hat>
311
+ # <type>top</type>
312
+ # </hat>
313
+ # </person>
314
+ #
315
+ # publish will also allow the +index+ action
316
+ # to render lists of objects.
317
+ # An example would be too big,
318
+ # but play with it a little on your own to see.
319
+ #
320
+ # publish takes only one optional option: <tt>only</tt>.
321
+ # This specifies which action to publish the resources for.
322
+ # By default, they're published for both +show+ and +index+.
323
+ # For example:
324
+ #
325
+ # # cats_controller.rb
326
+ # publish :json, :only => :index, :attributes => [:name, :breed]
327
+ #
328
+ # Then GET /cats.json would work, but GET /cats/294.json would fail.
329
+ def publish(*formats)
330
+ options = {
331
+ :only => [:show, :index]
332
+ }.merge(Hash === formats.last ? formats.pop : {})
333
+ raise "Must specify :attributes option" unless options[:attributes]
334
+
335
+ Array(options.delete(:only)).each do |action|
336
+ @publish[action] ||= []
337
+ formats.each do |format|
338
+ format = format.to_sym
339
+ @publish[action] << [format, proc do
340
+ render_action = [:json, :xml].include?(format) ? format : :text
341
+ render render_action => (plural_action? ? current_objects : current_object).serialize(format, options)
342
+ end]
343
+ end
344
+ end
345
+ end
346
+
347
+ # Specifies parent resources for the current resource.
348
+ # Each of these parents will be loaded automatically
349
+ # if the proper id parameter is given.
350
+ # For example,
351
+ #
352
+ # # cake_controller.rb
353
+ # belongs_to :baker, :customer
354
+ #
355
+ # Then on GET /bakers/12/cakes,
356
+ #
357
+ # params[:baker_id] #=> 12
358
+ # parent? #=> true
359
+ # parent_name #=> "baker"
360
+ # parent_model #=> Baker
361
+ # parent_object #=> Baker.find(12)
362
+ # current_objects #=> Baker.find(12).cakes
363
+ #
364
+ def belongs_to(*parents)
365
+ options = parents.extract_options!
366
+ @parents = parents.map(&:to_s)
367
+ if options[:shallow]
368
+ options[:shallow] = options[:shallow].to_s
369
+ raise ArgumentError, ":shallow needs the name of a parent resource" unless @parents.include? options[:shallow]
370
+ @shallow_parent = options[:shallow]
371
+ end
372
+ end
373
+
374
+ # Specifies a namespace for the resource model. It can be given as a
375
+ # Module::NameSpace, 'Module::NameSpace' (in a string), or
376
+ # 'module/name_space' (underscored form).
377
+ def model_namespace(ns)
378
+ @model_namespace = ns.to_s.camelize
379
+ end
380
+
381
+ # This method is only meant to be called internally.
382
+ #
383
+ # Returns whether or not the Builder's controller
384
+ # inherits make_resourceful settings from a parent controller.
385
+ def inherited?
386
+ @inherited
387
+ end
388
+
389
+ private
390
+
391
+ def apply_publish
392
+ @publish.each do |action, types|
393
+ @responses[action.to_sym] ||= []
394
+ @responses[action.to_sym] += types
395
+ end
396
+ end
397
+
398
+ def add_callback(type, *events, &block)
399
+ events.each do |event|
400
+ @callbacks[type][event.to_sym] ||= []
401
+ @callbacks[type][event.to_sym] << block
402
+ end
403
+ end
404
+ end
405
+ end