make_resourceful 1.0

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 (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