david-merb_resourceful 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.textile +67 -0
- data/Rakefile +51 -0
- data/TODO +7 -0
- data/lib/merb_resourceful/builder.rb +280 -0
- data/lib/merb_resourceful/controller.rb +8 -0
- data/lib/merb_resourceful/orms/datamapper_resource.rb +27 -0
- data/lib/merb_resourceful.rb +21 -0
- data/spec/config/init.rb +9 -0
- data/spec/merb_resourceful/datamapper_resourceful_controller_spec.rb +71 -0
- data/spec/merb_resourceful/resourceful_controller_spec.rb +441 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/views/create.html.erb +1 -0
- data/spec/views/edit.html.erb +1 -0
- data/spec/views/index.html.erb +5 -0
- data/spec/views/layout.another.html.erb +1 -0
- data/spec/views/new.html.erb +1 -0
- data/spec/views/show.html.erb +1 -0
- data/spec/views/update.html.erb +1 -0
- metadata +89 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 David Leal
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
h1. merb_resourceful
|
2
|
+
|
3
|
+
merb_resourceful is a plugin similar to resource_controller or make_resourceful for rails.
|
4
|
+
|
5
|
+
h2. Usage
|
6
|
+
|
7
|
+
Generate a new resource (this will change in the future)
|
8
|
+
|
9
|
+
merb-gen resource my_resource
|
10
|
+
|
11
|
+
Edit app/controllers/my_resources.rb and leave the class declaration only
|
12
|
+
|
13
|
+
class MyResources < Merb::Controller
|
14
|
+
resourceful
|
15
|
+
end
|
16
|
+
|
17
|
+
This gives you a basic controller with a few defaults (destroy isn't implemented yet).
|
18
|
+
|
19
|
+
h3. Nested resources
|
20
|
+
|
21
|
+
class MyResources < Merb::Controller
|
22
|
+
resourceful :belongs_to => :my_parent
|
23
|
+
# The children will be accessed through parent.[children association]
|
24
|
+
# E.g. my_parent.my_resources
|
25
|
+
# this will be configurable in the future
|
26
|
+
def my_parent
|
27
|
+
@foo ||= session.user.parents.get(params[:foo_id])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
h3. Scoped resources
|
32
|
+
|
33
|
+
class MyResources < Merb::Controller
|
34
|
+
resourceful :scope => :scope_method
|
35
|
+
# works just like :parent, but doesn't use @bar for routing
|
36
|
+
def scope_method
|
37
|
+
@bar ||= session.user.parents.get(params[:bar_id])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
h3. Per-action settings
|
42
|
+
|
43
|
+
class MyResources < Merb::Controller
|
44
|
+
resourceful do
|
45
|
+
index :layout => 'my_special_layout'
|
46
|
+
create :scope => :scope_for_create
|
47
|
+
end
|
48
|
+
def scope_for_create
|
49
|
+
@baz = get_me!
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
h3. Param injection
|
54
|
+
|
55
|
+
Merb already supports this with the defer_to method in the router, but I think this is way more explicit.
|
56
|
+
|
57
|
+
class MyResources < Merb::Controller
|
58
|
+
resourceful do
|
59
|
+
index :params => lambda {{ :creator => session.user }} # passes :creator as a param to MyResource.new
|
60
|
+
create :params => lambda {{ :creator => session.user }} # passes :creator as a param to the resource's update method
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
h2. Lots more to come
|
65
|
+
|
66
|
+
* More docs
|
67
|
+
* Implement destroy
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
|
4
|
+
require 'merb-core'
|
5
|
+
require 'merb-core/tasks/merb'
|
6
|
+
|
7
|
+
GEM_NAME = "merb_resourceful"
|
8
|
+
GEM_VERSION = "0.0.1"
|
9
|
+
AUTHOR = "David Leal"
|
10
|
+
EMAIL = "dgleal@gmail.com"
|
11
|
+
HOMEPAGE = "http://github.com/david/merb_resourceful"
|
12
|
+
SUMMARY = "merb_resourceful lends a little magic to common resource actions."
|
13
|
+
|
14
|
+
spec = Gem::Specification.new do |s|
|
15
|
+
s.rubyforge_project = 'merb'
|
16
|
+
s.name = GEM_NAME
|
17
|
+
s.version = GEM_VERSION
|
18
|
+
s.platform = Gem::Platform::RUBY
|
19
|
+
s.has_rdoc = true
|
20
|
+
s.extra_rdoc_files = ["README.textile", "LICENSE", 'TODO']
|
21
|
+
s.summary = SUMMARY
|
22
|
+
s.description = s.summary
|
23
|
+
s.author = AUTHOR
|
24
|
+
s.email = EMAIL
|
25
|
+
s.homepage = HOMEPAGE
|
26
|
+
s.add_dependency('merb', '>= 1.0')
|
27
|
+
s.require_path = 'lib'
|
28
|
+
s.files = %w(LICENSE README.textile Rakefile TODO) + Dir.glob("{lib,spec}/**/*")
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
33
|
+
pkg.gem_spec = spec
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "install the plugin as a gem"
|
37
|
+
task :install do
|
38
|
+
Merb::RakeHelper.install(GEM_NAME, :version => GEM_VERSION)
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "Uninstall the gem"
|
42
|
+
task :uninstall do
|
43
|
+
Merb::RakeHelper.uninstall(GEM_NAME, :version => GEM_VERSION)
|
44
|
+
end
|
45
|
+
|
46
|
+
desc "Create a gemspec file"
|
47
|
+
task :gemspec do
|
48
|
+
File.open("#{GEM_NAME}.gemspec", "w") do |file|
|
49
|
+
file.puts spec.to_ruby
|
50
|
+
end
|
51
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
TODO:
|
2
|
+
* Rewrite the tests using a sample merb app.
|
3
|
+
* Validate options
|
4
|
+
* Cleanup internal methods, so they can be overriden by the user
|
5
|
+
* Make sure all options make sense.
|
6
|
+
* Add passing a symbol to some options, besides lambdas, and that will call the corresponding method
|
7
|
+
* Documentation.
|
@@ -0,0 +1,280 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'controller'))
|
2
|
+
|
3
|
+
|
4
|
+
module Merb
|
5
|
+
module Plugins
|
6
|
+
module Resourceful
|
7
|
+
class Builder
|
8
|
+
ALL_ACTIONS = %w(index show new create edit update destroy)
|
9
|
+
|
10
|
+
def initialize(controller_class, options)
|
11
|
+
@controller_class = controller_class
|
12
|
+
@options = options
|
13
|
+
@resource_class = @controller_class.name.demodulize.singularize
|
14
|
+
@resource_plural = @controller_class.name.demodulize.underscore
|
15
|
+
@resource_name = @resource_plural.singularize
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
def build(&block)
|
20
|
+
@controller_class.send :include, ::Merb::Plugins::Resourceful::Controller
|
21
|
+
@controller_class.const_set('RESOURCE_NAME', @resource_name)
|
22
|
+
@controller_class.const_set('RESOURCES_NAME', @resource_plural)
|
23
|
+
|
24
|
+
def_source
|
25
|
+
|
26
|
+
ALL_ACTIONS.each { |action| send(action) }
|
27
|
+
|
28
|
+
@controller_class.class_eval <<-EOF
|
29
|
+
def resources_set(r)
|
30
|
+
@#{@resource_plural} = r
|
31
|
+
end
|
32
|
+
|
33
|
+
def resource_set(r)
|
34
|
+
@#{@resource_name} = r
|
35
|
+
end
|
36
|
+
|
37
|
+
def resource_ivar
|
38
|
+
@#{@resource_name}
|
39
|
+
end
|
40
|
+
EOF
|
41
|
+
|
42
|
+
if block_given? then instance_eval(&block) end
|
43
|
+
end
|
44
|
+
|
45
|
+
def index(options = {})
|
46
|
+
def_source(options, :index)
|
47
|
+
|
48
|
+
filter = options[:filter] && ".#{options[:filter]}"
|
49
|
+
params = options[:params] && "(resource_params_for_index)"
|
50
|
+
@controller_class.class_eval <<-EOF
|
51
|
+
def resource_index
|
52
|
+
resources_set(resource_list(resource_source_for_index)#{filter}#{params})
|
53
|
+
end
|
54
|
+
EOF
|
55
|
+
|
56
|
+
@controller_class.class_eval do
|
57
|
+
def index
|
58
|
+
display resource_index, render_options_for_index
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
inject_render_options(options, :index)
|
63
|
+
inject_params(options, :index)
|
64
|
+
end
|
65
|
+
|
66
|
+
def show(options = {})
|
67
|
+
def_source(options, :show)
|
68
|
+
|
69
|
+
@controller_class.class_eval do
|
70
|
+
def show
|
71
|
+
r = resource_get_for_show(params[:id]) or raise Merb::Controller::NotFound
|
72
|
+
display r, render_options_for_show
|
73
|
+
end
|
74
|
+
|
75
|
+
protected
|
76
|
+
|
77
|
+
def resource_get_for_show(id)
|
78
|
+
resource_get(resource_source_for_show, id)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
inject_render_options(options, :show)
|
83
|
+
end
|
84
|
+
|
85
|
+
def new(options = {})
|
86
|
+
def_source(options, :new)
|
87
|
+
|
88
|
+
@controller_class.class_eval do
|
89
|
+
def new
|
90
|
+
only_provides :html
|
91
|
+
display resource_new, render_options_for_new
|
92
|
+
end
|
93
|
+
|
94
|
+
protected
|
95
|
+
|
96
|
+
def resource_new
|
97
|
+
resource_initialize(resource_source_for_new)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
inject_render_options(options, :new)
|
102
|
+
end
|
103
|
+
|
104
|
+
def create(options = {})
|
105
|
+
def_source(options, :create)
|
106
|
+
|
107
|
+
@controller_class.class_eval do
|
108
|
+
def create
|
109
|
+
r = resource_initialize(resource_source_for_create,
|
110
|
+
params[self.class::RESOURCE_NAME].merge!(resource_params_for_create))
|
111
|
+
if r.save
|
112
|
+
resource_created(r)
|
113
|
+
else
|
114
|
+
message[:error] = "#{self.class::RESOURCE_NAME.humanize} failed to be created"
|
115
|
+
render :new, render_options_for_create
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
protected
|
120
|
+
|
121
|
+
def resource_created(resrc)
|
122
|
+
redirect resource_created_route, :message => { :notice => resource_created_message(resrc) }
|
123
|
+
end
|
124
|
+
|
125
|
+
def resource_created_message(resrc)
|
126
|
+
"#{self.class::RESOURCE_NAME.singularize} created successfully."
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
inject_route(options, :create)
|
131
|
+
inject_params(options, :create)
|
132
|
+
inject_render_options(options, :create)
|
133
|
+
end
|
134
|
+
|
135
|
+
def edit(options = {})
|
136
|
+
def_source(options, :edit)
|
137
|
+
|
138
|
+
@controller_class.class_eval do
|
139
|
+
def edit
|
140
|
+
only_provides :html
|
141
|
+
r = resource_get(resource_source_for_edit, params[:id]) or raise ::Merb::Controller::NotFound
|
142
|
+
display r, render_options_for_edit
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
inject_render_options(options, :edit)
|
147
|
+
end
|
148
|
+
|
149
|
+
def update(options = {})
|
150
|
+
def_source(options, :update)
|
151
|
+
|
152
|
+
@controller_class.class_eval do
|
153
|
+
def update
|
154
|
+
r = resource_get(resource_source_for_update, params[:id]) or raise ::Merb::Controller::NotFound
|
155
|
+
if r.update(params[self.class::RESOURCE_NAME].merge!(resource_params_for_update))
|
156
|
+
resource_updated(r)
|
157
|
+
else
|
158
|
+
message[:error] = "#{RESOURCE_NAME.humanize} failed to be updated"
|
159
|
+
render :edit, render_options_for_update
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
protected
|
164
|
+
|
165
|
+
def resource_updated(resrc)
|
166
|
+
redirect resource_updated_route, :message => {:notice => resource_updated_message(resrc)}
|
167
|
+
end
|
168
|
+
|
169
|
+
def resource_updated_message(resrc)
|
170
|
+
"#{self.class::RESOURCE_NAME.humanize} updated successfully."
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
inject_route(options, :update)
|
175
|
+
inject_params(options, :update)
|
176
|
+
inject_render_options(options, :update)
|
177
|
+
end
|
178
|
+
|
179
|
+
def destroy
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
|
184
|
+
def has_parent?(options)
|
185
|
+
!! (options[:belongs_to] || @options[:belongs_to])
|
186
|
+
end
|
187
|
+
|
188
|
+
def inject_route(options, method_name)
|
189
|
+
to = (options[:to] && options[:to].inspect) || "resource_ivar"
|
190
|
+
|
191
|
+
if has_parent?(options)
|
192
|
+
@controller_class.class_eval <<-EOF
|
193
|
+
protected
|
194
|
+
def resource_#{method_name}d_route
|
195
|
+
resource(resource_parent_get_for_#{method_name}, #{to})
|
196
|
+
end
|
197
|
+
EOF
|
198
|
+
else
|
199
|
+
@controller_class.class_eval <<-EOF
|
200
|
+
protected
|
201
|
+
def resource_#{method_name}d_route
|
202
|
+
resource(#{to})
|
203
|
+
end
|
204
|
+
EOF
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def inject_render_options(options, method_name)
|
209
|
+
if options[:layout]
|
210
|
+
@controller_class.class_eval do
|
211
|
+
protected
|
212
|
+
define_method "render_options_for_#{method_name}" do
|
213
|
+
{ :layout => options[:layout] }
|
214
|
+
end
|
215
|
+
end
|
216
|
+
else
|
217
|
+
@controller_class.class_eval <<-EOF
|
218
|
+
protected
|
219
|
+
def render_options_for_#{method_name}() {} end
|
220
|
+
EOF
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def inject_params(options, method_name)
|
225
|
+
case options[:params]
|
226
|
+
when Proc
|
227
|
+
@controller_class.class_eval do
|
228
|
+
protected
|
229
|
+
define_method "resource_params_for_#{method_name}", &options[:params]
|
230
|
+
end
|
231
|
+
else
|
232
|
+
@controller_class.class_eval <<-EOF
|
233
|
+
protected
|
234
|
+
def resource_params_for_#{method_name}() {} end
|
235
|
+
EOF
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def def_source(options = {}, method_name = nil)
|
240
|
+
source = options[:belongs_to] || options[:scope] || @options[:belongs_to] || @options[:scope]
|
241
|
+
|
242
|
+
case source
|
243
|
+
when Symbol
|
244
|
+
def_source_with_scope(source, method_name)
|
245
|
+
else
|
246
|
+
if method_name.nil?
|
247
|
+
@controller_class.class_eval <<-EOF
|
248
|
+
protected
|
249
|
+
def resource_source
|
250
|
+
#{@resource_class}
|
251
|
+
end
|
252
|
+
EOF
|
253
|
+
else
|
254
|
+
@controller_class.class_eval do
|
255
|
+
alias_method "resource_source_for_#{method_name}", :resource_source
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def def_source_with_scope(source, method_name = nil)
|
262
|
+
suffix = method_name && "_for_#{method_name}"
|
263
|
+
|
264
|
+
s = <<-EOF
|
265
|
+
protected
|
266
|
+
def resource_source#{suffix}
|
267
|
+
#{source}.#{@resource_plural}
|
268
|
+
end
|
269
|
+
|
270
|
+
def resource_parent_get#{suffix}
|
271
|
+
#{source}
|
272
|
+
end
|
273
|
+
EOF
|
274
|
+
|
275
|
+
@controller_class.class_eval s
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
if Merb.orm == :datamapper
|
2
|
+
module Merb
|
3
|
+
module Plugins
|
4
|
+
module Resourceful
|
5
|
+
module ORMS
|
6
|
+
module DataMapperResource
|
7
|
+
def resource_list(source)
|
8
|
+
resources_set(source.all)
|
9
|
+
end
|
10
|
+
|
11
|
+
def resource_initialize(source, attrs = {})
|
12
|
+
resource_set(source.new(attrs))
|
13
|
+
end
|
14
|
+
|
15
|
+
def resource_get(source, id)
|
16
|
+
resource_set(source.get(id))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Controller
|
22
|
+
include ::Merb::Plugins::Resourceful::ORMS::DataMapperResource
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'english/style_orm'
|
2
|
+
require File.join(File.dirname(__FILE__), 'merb_resourceful/builder')
|
3
|
+
require File.join(File.dirname(__FILE__), 'merb_resourceful/orms/datamapper_resource')
|
4
|
+
|
5
|
+
if defined?(Merb)
|
6
|
+
module Merb
|
7
|
+
module Plugins
|
8
|
+
module Resourceful
|
9
|
+
module ClassMethods
|
10
|
+
def resourceful(options = {}, &block)
|
11
|
+
::Merb::Plugins::Resourceful::Builder.new(self, options).build(&block)
|
12
|
+
end
|
13
|
+
end # ClassMethods
|
14
|
+
end # Resourceful
|
15
|
+
end # Plugins
|
16
|
+
|
17
|
+
class Controller
|
18
|
+
extend ::Merb::Plugins::Resourceful::ClassMethods
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/spec/config/init.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
Merb::Config.use do |c|
|
2
|
+
c[:default_cookie_domain] = "resourceful.davidleal.com"
|
3
|
+
c[:session_id_key] = "key"
|
4
|
+
c[:session_secret_key] = "shhhhhh"
|
5
|
+
c[:session_expiry] = Merb::Const::WEEK * 4
|
6
|
+
c[:log_auto_flush ] = true
|
7
|
+
c[:log_level] = :debug
|
8
|
+
c[:log_file] = File.join(File.dirname(__FILE__), '..', '..', 'log', 'test.log')
|
9
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'spec_helper')
|
2
|
+
require File.join(File.dirname(__FILE__), 'resourceful_controller_spec')
|
3
|
+
|
4
|
+
if Merb.orm == :datamapper
|
5
|
+
given "a book exists" do
|
6
|
+
Book.all.destroy!
|
7
|
+
|
8
|
+
@book = (@shelf ? @shelf.books : Book).create(:title => 'Code Complete')
|
9
|
+
end
|
10
|
+
|
11
|
+
given "a shelf exists" do
|
12
|
+
Shelf.all.destroy!
|
13
|
+
|
14
|
+
@shelf = Shelf.create
|
15
|
+
end
|
16
|
+
|
17
|
+
given "2 books exist" do
|
18
|
+
@book = (@shelf ? @shelf.books : Book).create(:title => 'Code Complete')
|
19
|
+
@shelf2 = Shelf.create
|
20
|
+
@book2 = @shelf2.books.create(:title => 'The Practice of Programming')
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "DataMapper resourceful controller" do
|
24
|
+
before :all do
|
25
|
+
DataMapper.setup(:default, "sqlite3::memory:")
|
26
|
+
|
27
|
+
class Book
|
28
|
+
include DataMapper::Resource
|
29
|
+
|
30
|
+
property :id, Serial
|
31
|
+
property :title, String, :length => 1..255
|
32
|
+
property :optional, String
|
33
|
+
|
34
|
+
def self.zee_filter(*args)
|
35
|
+
if param = args.pop
|
36
|
+
[Book.new(:title => param)]
|
37
|
+
else
|
38
|
+
[Book.new(:title => "no params")]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Shelf
|
44
|
+
include DataMapper::Resource
|
45
|
+
|
46
|
+
property :id, Serial
|
47
|
+
|
48
|
+
has n, :books
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
after :all do
|
53
|
+
Object.send(:remove_const, :Book)
|
54
|
+
end
|
55
|
+
|
56
|
+
before do
|
57
|
+
Shelf.auto_migrate!
|
58
|
+
Book.auto_migrate!
|
59
|
+
end
|
60
|
+
|
61
|
+
it_should_behave_like "resourceful controller"
|
62
|
+
|
63
|
+
def find_single_book
|
64
|
+
Book.first
|
65
|
+
end
|
66
|
+
|
67
|
+
def find_book_in_shelf
|
68
|
+
@shelf.books.last
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,441 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
2
|
+
|
3
|
+
|
4
|
+
describe "resourceful controller", :shared => true do
|
5
|
+
|
6
|
+
before :all do
|
7
|
+
Merb.push_path(:view, File.join(File.dirname(__FILE__), '..', 'views'))
|
8
|
+
end
|
9
|
+
|
10
|
+
before do
|
11
|
+
class AbstractBooks < Merb::Controller
|
12
|
+
def _template_location(action, type = nil, controller = controller_name)
|
13
|
+
controller == "layout" ? "layout.#{action}.#{type}" : "#{action}.#{type}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class ShelvedBooks < AbstractBooks
|
18
|
+
def shelf
|
19
|
+
@shelf ||= Shelf.all.last
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
after do
|
26
|
+
Object.send(:remove_const, :Books)
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "common behavior", :shared => true do
|
30
|
+
describe "resource(*, :books)" do
|
31
|
+
describe "GET" do
|
32
|
+
before(:each) do
|
33
|
+
with_resourceful
|
34
|
+
|
35
|
+
@response = request(resource(*request_for_books))
|
36
|
+
end
|
37
|
+
|
38
|
+
it "responds successfully" do
|
39
|
+
@response.should be_successful
|
40
|
+
end
|
41
|
+
|
42
|
+
it "has a list of books" do
|
43
|
+
@response.should have_xpath("//ul")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "GET", :given => "a book exists" do
|
48
|
+
before(:each) do
|
49
|
+
with_resourceful
|
50
|
+
|
51
|
+
@response = request(resource(*request_for_books))
|
52
|
+
end
|
53
|
+
|
54
|
+
it "has a list of books" do
|
55
|
+
@response.should have_xpath("//ul/li")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "a successful POST" do
|
60
|
+
before(:each) do
|
61
|
+
with_resourceful
|
62
|
+
|
63
|
+
@response = request(resource(*request_for_books), :method => "POST",
|
64
|
+
:params => { :book => { :title => "The C Programming Language" }})
|
65
|
+
end
|
66
|
+
|
67
|
+
it "redirects to resource(*, @book)" do
|
68
|
+
@response.should redirect_to(resource(*request_for_book),
|
69
|
+
:message => {:notice => "book successfully created"})
|
70
|
+
end
|
71
|
+
|
72
|
+
it "creates the resource" do
|
73
|
+
find_book.should_not be_nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "GET", "with :filter", :given => "a book exists" do
|
78
|
+
before(:each) do
|
79
|
+
with_resourceful do
|
80
|
+
index :filter => :zee_filter
|
81
|
+
end
|
82
|
+
|
83
|
+
@response = request(resource(*request_for_books))
|
84
|
+
end
|
85
|
+
|
86
|
+
it "has a list of filtered books" do
|
87
|
+
@response.should have_xpath("//ul/li[. = 'no params']")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "GET", "with :filter and :params", :given => "a book exists" do
|
92
|
+
before(:each) do
|
93
|
+
with_resourceful do
|
94
|
+
index :filter => :zee_filter, :params => lambda { "whoa" }
|
95
|
+
end
|
96
|
+
|
97
|
+
@response = request(resource(*request_for_books))
|
98
|
+
end
|
99
|
+
|
100
|
+
it "has a list of filtered books" do
|
101
|
+
@response.should have_xpath("//ul/li[. = 'whoa']")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "a successful POST", "with :params" do
|
106
|
+
before(:each) do
|
107
|
+
with_resourceful do
|
108
|
+
create :params => lambda {{ :optional => "is not null!" }}
|
109
|
+
end
|
110
|
+
|
111
|
+
@response = request(resource(*request_for_books), :method => "POST",
|
112
|
+
:params => { :book => { :title => "The C Programming Language" }})
|
113
|
+
end
|
114
|
+
|
115
|
+
it "redirects to resource(*, @book)" do
|
116
|
+
@response.should redirect_to(resource(*request_for_book),
|
117
|
+
:message => {:notice => "book successfully created"})
|
118
|
+
end
|
119
|
+
|
120
|
+
it "adds the value of optional to the book" do
|
121
|
+
find_book.optional.should_not be_nil
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe "a successful POST", "with :to" do
|
126
|
+
before(:each) do
|
127
|
+
with_resourceful do
|
128
|
+
create :to => :books
|
129
|
+
end
|
130
|
+
|
131
|
+
@response = request(resource(*request_for_books), :method => "POST",
|
132
|
+
:params => { :book => { :title => "The C Programming Language" }})
|
133
|
+
end
|
134
|
+
|
135
|
+
it "redirects to resource(*, :books)" do
|
136
|
+
@response.should redirect_to(resource(*request_for_books),
|
137
|
+
:message => {:notice => "book successfully created"})
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "resource(*, :books, :new)" do
|
143
|
+
before(:each) do
|
144
|
+
with_resourceful
|
145
|
+
|
146
|
+
@response = request(resource(*request_for_new_book))
|
147
|
+
end
|
148
|
+
|
149
|
+
it "responds successfully" do
|
150
|
+
@response.should be_successful
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe "resource(*, @book, :edit)", :given => "a book exists" do
|
155
|
+
before(:each) do
|
156
|
+
with_resourceful
|
157
|
+
|
158
|
+
@response = request(resource(*request_for_edit_book))
|
159
|
+
end
|
160
|
+
|
161
|
+
it "responds successfully" do
|
162
|
+
@response.should be_successful
|
163
|
+
end
|
164
|
+
|
165
|
+
it "presents the resource being edited" do
|
166
|
+
@response.should have_xpath("//*[contains(., 'Edit Code Complete')]")
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
describe "resource(*, @book)", :given => "a book exists" do
|
171
|
+
describe "GET" do
|
172
|
+
before(:each) do
|
173
|
+
with_resourceful
|
174
|
+
|
175
|
+
@response = request(resource(*request_for_book))
|
176
|
+
end
|
177
|
+
|
178
|
+
it "responds successfully" do
|
179
|
+
@response.should be_successful
|
180
|
+
end
|
181
|
+
|
182
|
+
it "shows the right book" do
|
183
|
+
@response.should have_xpath("//*[contains(., 'Code Complete')]")
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe "PUT" do
|
188
|
+
before(:each) do
|
189
|
+
with_resourceful
|
190
|
+
|
191
|
+
@response = request(resource(*request_for_book), :method => "PUT",
|
192
|
+
:params => { :book => {:id => @book.id, :title => "Code Complete 2nd Edition."} })
|
193
|
+
end
|
194
|
+
|
195
|
+
it "redirect to the book show action" do
|
196
|
+
@response.should redirect_to(resource(*request_for_book))
|
197
|
+
end
|
198
|
+
|
199
|
+
it "updates the book" do
|
200
|
+
Book.first.title.should == "Code Complete 2nd Edition."
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
describe "PUT", "with :params" do
|
205
|
+
before(:each) do
|
206
|
+
with_resourceful do
|
207
|
+
update :params => lambda {{ :optional => "is not null!" }}
|
208
|
+
end
|
209
|
+
|
210
|
+
@response = request(resource(*request_for_book), :method => "PUT",
|
211
|
+
:params => { :book => {:id => @book.id, :title => "Code Complete 2nd Edition."} })
|
212
|
+
end
|
213
|
+
|
214
|
+
it "redirect to the book show action" do
|
215
|
+
@response.should redirect_to(resource(*request_for_book))
|
216
|
+
end
|
217
|
+
|
218
|
+
it "updates the book with the extra param" do
|
219
|
+
Book.first.optional.should_not be_nil
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
describe "a successful DELETE" do
|
224
|
+
before(:each) do
|
225
|
+
pending do
|
226
|
+
@response = request(resource(*request_delete), :method => "DELETE")
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
it "should redirect to the index action" do
|
231
|
+
pending do
|
232
|
+
@response.should redirect_to(resource(*request_for_books))
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
describe "GET" do
|
238
|
+
before(:each) do
|
239
|
+
with_resourceful do
|
240
|
+
show :layout => :another
|
241
|
+
end
|
242
|
+
|
243
|
+
@response = request(resource(*request_for_book))
|
244
|
+
end
|
245
|
+
|
246
|
+
it "has a different layout" do
|
247
|
+
@response.should have_xpath("//*[contains(., 'Another Layout')]")
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
describe "action-specific scopes", :shared => true do
|
254
|
+
before do
|
255
|
+
Books.class_eval do
|
256
|
+
def shelf
|
257
|
+
@shelf ||= Shelf.all.last
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
describe "resource(*, @book)", :given => "a book exists" do
|
263
|
+
describe "GET", "using :scope", :given => "2 books exist" do
|
264
|
+
before(:each) do
|
265
|
+
with_resourceful do
|
266
|
+
show :scope => :shelf
|
267
|
+
end
|
268
|
+
|
269
|
+
@response = request(resource(*request_for_book))
|
270
|
+
end
|
271
|
+
|
272
|
+
it "responds successfully" do
|
273
|
+
@response.should be_successful
|
274
|
+
end
|
275
|
+
|
276
|
+
it "shows the right book" do
|
277
|
+
@response.should have_xpath("//*[contains(., 'The Practice of Programming')]")
|
278
|
+
end
|
279
|
+
|
280
|
+
def request_for_book
|
281
|
+
@book2
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
describe "a successful POST", "using :scope", :given => "a shelf exists" do
|
286
|
+
before(:each) do
|
287
|
+
with_resourceful do
|
288
|
+
create :scope => :shelf
|
289
|
+
end
|
290
|
+
|
291
|
+
@response = request(resource(*request_for_books), :method => "POST",
|
292
|
+
:params => { :book => { :title => "The C Programming Language" }})
|
293
|
+
end
|
294
|
+
|
295
|
+
it "redirects to resource(*, @book)" do
|
296
|
+
@response.should redirect_to(resource(*find_book),
|
297
|
+
:message => {:notice => "book successfully created"})
|
298
|
+
end
|
299
|
+
|
300
|
+
it "creates the book" do
|
301
|
+
find_book.title.should == "The C Programming Language"
|
302
|
+
end
|
303
|
+
|
304
|
+
def find_book
|
305
|
+
find_book_in_shelf
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
describe "single resource" do
|
312
|
+
before :all do
|
313
|
+
Merb::Router.prepare do |r|
|
314
|
+
identify :id do
|
315
|
+
r.resources :books
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
before do
|
321
|
+
class Books < AbstractBooks
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def request_for_books
|
326
|
+
:books
|
327
|
+
end
|
328
|
+
|
329
|
+
def request_for_book
|
330
|
+
@book || find_book
|
331
|
+
end
|
332
|
+
|
333
|
+
def request_for_new_book
|
334
|
+
[:books, :new]
|
335
|
+
end
|
336
|
+
|
337
|
+
def request_for_edit_book
|
338
|
+
[@book, :edit]
|
339
|
+
end
|
340
|
+
|
341
|
+
it_should_behave_like "common behavior"
|
342
|
+
it_should_behave_like "action-specific scopes"
|
343
|
+
|
344
|
+
def find_book
|
345
|
+
find_single_book
|
346
|
+
end
|
347
|
+
|
348
|
+
def with_resourceful(&block)
|
349
|
+
Books.class_eval do
|
350
|
+
resourceful &block
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
describe "single resource", "using :scope", :given => "a shelf exists" do
|
356
|
+
before :all do
|
357
|
+
Merb::Router.prepare do |r|
|
358
|
+
identify :id do
|
359
|
+
r.resources :books
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
before do
|
365
|
+
class Books < ShelvedBooks; end
|
366
|
+
end
|
367
|
+
|
368
|
+
def request_for_books
|
369
|
+
:books
|
370
|
+
end
|
371
|
+
|
372
|
+
def request_for_book
|
373
|
+
@book || find_book
|
374
|
+
end
|
375
|
+
|
376
|
+
def request_for_new_book
|
377
|
+
[:books, :new]
|
378
|
+
end
|
379
|
+
|
380
|
+
def request_for_edit_book
|
381
|
+
[@book, :edit]
|
382
|
+
end
|
383
|
+
|
384
|
+
it_should_behave_like "common behavior"
|
385
|
+
it_should_behave_like "action-specific scopes"
|
386
|
+
|
387
|
+
def find_book
|
388
|
+
find_book_in_shelf
|
389
|
+
end
|
390
|
+
|
391
|
+
def with_resourceful(&block)
|
392
|
+
Books.class_eval do
|
393
|
+
resourceful :scope => :shelf, &block
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
describe "nested resource", "through method", :given => "a shelf exists" do
|
399
|
+
before :all do
|
400
|
+
Merb::Router.prepare do |r|
|
401
|
+
identify :id do
|
402
|
+
r.resources :shelves do
|
403
|
+
r.resources :books
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
before do
|
410
|
+
class Books < ShelvedBooks; end
|
411
|
+
end
|
412
|
+
|
413
|
+
def request_for_books
|
414
|
+
[@shelf, :books]
|
415
|
+
end
|
416
|
+
|
417
|
+
def request_for_book
|
418
|
+
[@shelf, @book || find_book]
|
419
|
+
end
|
420
|
+
|
421
|
+
def request_for_new_book
|
422
|
+
[@shelf, :books, :new]
|
423
|
+
end
|
424
|
+
|
425
|
+
def request_for_edit_book
|
426
|
+
[@shelf, @book, :edit]
|
427
|
+
end
|
428
|
+
|
429
|
+
it_should_behave_like "common behavior"
|
430
|
+
|
431
|
+
def find_book
|
432
|
+
find_book_in_shelf
|
433
|
+
end
|
434
|
+
|
435
|
+
def with_resourceful(&block)
|
436
|
+
Books.class_eval do
|
437
|
+
resourceful :belongs_to => :shelf, &block
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
require 'merb-core'
|
4
|
+
|
5
|
+
$:.push File.join(File.dirname(__FILE__), '..', 'lib')
|
6
|
+
|
7
|
+
Spec::Runner.configure do |config|
|
8
|
+
config.include(Merb::Test::ViewHelper)
|
9
|
+
config.include(Merb::Test::RouteHelper)
|
10
|
+
config.include(Merb::Test::ControllerHelper)
|
11
|
+
end
|
12
|
+
|
13
|
+
Merb.start :environment => 'test', :init_file => File.join(File.dirname(__FILE__), 'config', 'init.rb')
|
14
|
+
|
15
|
+
use_orm (ENV['ORM'] || :datamapper).to_sym
|
16
|
+
|
17
|
+
require 'merb_resourceful'
|
18
|
+
require 'ruby-debug'
|
19
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= @book.title %>
|
@@ -0,0 +1 @@
|
|
1
|
+
Edit <%= @book.title %>
|
@@ -0,0 +1 @@
|
|
1
|
+
Another Layout
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= @book.title %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= @book.title %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= @book.title %>
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: david-merb_resourceful
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Leal
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-01 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: merb
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "1.0"
|
23
|
+
version:
|
24
|
+
description: merb_resourceful lends a little magic to common resource actions.
|
25
|
+
email: dgleal@gmail.com
|
26
|
+
executables: []
|
27
|
+
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files:
|
31
|
+
- README.textile
|
32
|
+
- LICENSE
|
33
|
+
- TODO
|
34
|
+
files:
|
35
|
+
- LICENSE
|
36
|
+
- README.textile
|
37
|
+
- Rakefile
|
38
|
+
- TODO
|
39
|
+
- lib/merb_resourceful
|
40
|
+
- lib/merb_resourceful/controller.rb
|
41
|
+
- lib/merb_resourceful/orms
|
42
|
+
- lib/merb_resourceful/orms/datamapper_resource.rb
|
43
|
+
- lib/merb_resourceful/builder.rb
|
44
|
+
- lib/merb_resourceful.rb
|
45
|
+
- spec/merb_resourceful
|
46
|
+
- spec/merb_resourceful/datamapper_resourceful_controller_spec.rb
|
47
|
+
- spec/merb_resourceful/resourceful_controller_spec.rb
|
48
|
+
- spec/merb_resourceful/log
|
49
|
+
- spec/merb_resourceful/log/merb_test.log
|
50
|
+
- spec/merb_resourceful/log/merb.main.pid
|
51
|
+
- spec/spec_helper.rb
|
52
|
+
- spec/views
|
53
|
+
- spec/views/create.html.erb
|
54
|
+
- spec/views/edit.html.erb
|
55
|
+
- spec/views/new.html.erb
|
56
|
+
- spec/views/layout.another.html.erb
|
57
|
+
- spec/views/update.html.erb
|
58
|
+
- spec/views/show.html.erb
|
59
|
+
- spec/views/index.html.erb
|
60
|
+
- spec/config
|
61
|
+
- spec/config/init.rb
|
62
|
+
has_rdoc: true
|
63
|
+
homepage: http://github.com/david/merb_resourceful
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options: []
|
66
|
+
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: "0"
|
74
|
+
version:
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: "0"
|
80
|
+
version:
|
81
|
+
requirements: []
|
82
|
+
|
83
|
+
rubyforge_project: merb
|
84
|
+
rubygems_version: 1.2.0
|
85
|
+
signing_key:
|
86
|
+
specification_version: 2
|
87
|
+
summary: merb_resourceful lends a little magic to common resource actions.
|
88
|
+
test_files: []
|
89
|
+
|