david-merb_resourceful 0.0.1

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/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,8 @@
1
+ module Merb
2
+ module Plugins
3
+ module Resourceful
4
+ module Controller
5
+ end
6
+ end
7
+ end
8
+ 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
@@ -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
@@ -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,5 @@
1
+ <ul>
2
+ <% for b in @books %>
3
+ <li><%= b.title %></li>
4
+ <% end -%>
5
+ </ul>
@@ -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
+