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.
- data/Rakefile +31 -0
- data/Readme.rdoc +229 -0
- data/VERSION +1 -0
- data/lib/make_resourceful.rb +11 -0
- data/lib/resourceful/base.rb +63 -0
- data/lib/resourceful/builder.rb +405 -0
- data/lib/resourceful/default/accessors.rb +418 -0
- data/lib/resourceful/default/actions.rb +101 -0
- data/lib/resourceful/default/callbacks.rb +51 -0
- data/lib/resourceful/default/responses.rb +118 -0
- data/lib/resourceful/default/urls.rb +136 -0
- data/lib/resourceful/generators/resourceful_scaffold/resourceful_scaffold_generator.rb +87 -0
- data/lib/resourceful/generators/resourceful_scaffold/templates/controller.rb +5 -0
- data/lib/resourceful/generators/resourceful_scaffold/templates/fixtures.yml +10 -0
- data/lib/resourceful/generators/resourceful_scaffold/templates/functional_test.rb +50 -0
- data/lib/resourceful/generators/resourceful_scaffold/templates/helper.rb +2 -0
- data/lib/resourceful/generators/resourceful_scaffold/templates/migration.rb +13 -0
- data/lib/resourceful/generators/resourceful_scaffold/templates/model.rb +2 -0
- data/lib/resourceful/generators/resourceful_scaffold/templates/unit_test.rb +7 -0
- data/lib/resourceful/generators/resourceful_scaffold/templates/view__form.haml +5 -0
- data/lib/resourceful/generators/resourceful_scaffold/templates/view_edit.haml +11 -0
- data/lib/resourceful/generators/resourceful_scaffold/templates/view_index.haml +5 -0
- data/lib/resourceful/generators/resourceful_scaffold/templates/view_new.haml +9 -0
- data/lib/resourceful/generators/resourceful_scaffold/templates/view_partial.haml +12 -0
- data/lib/resourceful/generators/resourceful_scaffold/templates/view_show.haml +14 -0
- data/lib/resourceful/maker.rb +92 -0
- data/lib/resourceful/response.rb +33 -0
- data/lib/resourceful/serialize.rb +185 -0
- data/spec/accessors_spec.rb +474 -0
- data/spec/actions_spec.rb +310 -0
- data/spec/base_spec.rb +12 -0
- data/spec/builder_spec.rb +332 -0
- data/spec/callbacks_spec.rb +71 -0
- data/spec/integration_spec.rb +394 -0
- data/spec/maker_spec.rb +91 -0
- data/spec/response_spec.rb +37 -0
- data/spec/responses_spec.rb +314 -0
- data/spec/serialize_spec.rb +133 -0
- data/spec/urls_spec.rb +282 -0
- 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
|