merb_component 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README +34 -0
- data/Rakefile +87 -0
- data/TODO +5 -0
- data/lib/merb_component.rb +20 -0
- data/lib/merb_component/controller_ext.rb +217 -0
- data/lib/merb_component/merbtasks.rb +6 -0
- data/lib/merb_component/resource_ext.rb +11 -0
- data/lib/merb_component/router_ext.rb +8 -0
- data/spec/admin_spec.rb +106 -0
- data/spec/fixture/app/controllers/admin.rb +7 -0
- data/spec/fixture/app/controllers/application.rb +2 -0
- data/spec/fixture/app/controllers/comments.rb +16 -0
- data/spec/fixture/app/controllers/posts.rb +14 -0
- data/spec/fixture/app/models/comment.rb +10 -0
- data/spec/fixture/app/models/post.rb +7 -0
- data/spec/fixture/app/views/admin/show.html.erb +4 -0
- data/spec/fixture/app/views/comments/edit.html.erb +11 -0
- data/spec/fixture/app/views/comments/index.atom.builder +21 -0
- data/spec/fixture/app/views/comments/index.html.erb +15 -0
- data/spec/fixture/app/views/comments/new.html.erb +6 -0
- data/spec/fixture/app/views/layout/application.html.erb +11 -0
- data/spec/fixture/app/views/posts/index.html.erb +7 -0
- data/spec/fixture/app/views/posts/show.html.erb +6 -0
- data/spec/fixture/config/router.rb +11 -0
- data/spec/merb_component_spec.rb +26 -0
- data/spec/posts_spec.rb +173 -0
- data/spec/spec_helper.rb +40 -0
- metadata +112 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 YOUR NAME
|
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
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
merb_component
|
2
|
+
=============
|
3
|
+
|
4
|
+
Merb plugin that provides composition of controllers.
|
5
|
+
|
6
|
+
Example of use:
|
7
|
+
|
8
|
+
In config/router.rb:
|
9
|
+
|
10
|
+
resources :posts do |post|
|
11
|
+
post.aggregates :comments
|
12
|
+
end
|
13
|
+
resource :admin, :controller => :admin do |admin|
|
14
|
+
admin.aggregates :comments
|
15
|
+
end
|
16
|
+
|
17
|
+
In controllers:
|
18
|
+
|
19
|
+
class Posts < Application
|
20
|
+
aggregates :comments
|
21
|
+
|
22
|
+
class Admin < Application
|
23
|
+
aggregates :show => :comments
|
24
|
+
|
25
|
+
In views:
|
26
|
+
|
27
|
+
Content of the user (id is 2) goes here
|
28
|
+
<%= component :users, :show, :id => 2 %>
|
29
|
+
|
30
|
+
For detail, you can see spec/fixture as an example.
|
31
|
+
|
32
|
+
Enjoy!
|
33
|
+
|
34
|
+
Genki Takiuchi
|
data/Rakefile
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
|
4
|
+
require 'merb-core'
|
5
|
+
require 'merb-core/tasks/merb'
|
6
|
+
|
7
|
+
GEM_NAME = "merb_component"
|
8
|
+
GEM_VERSION = "0.2.3"
|
9
|
+
AUTHOR = "Genki Takiuchi"
|
10
|
+
EMAIL = "genki@s21g.com"
|
11
|
+
HOMEPAGE = "http://blog.s21g.com/genki"
|
12
|
+
RUBYFORGE_PROJECT = 'asakusarb'
|
13
|
+
SUMMARY = "Merb plugin that provides composition of controllers."
|
14
|
+
|
15
|
+
spec = Gem::Specification.new do |s|
|
16
|
+
s.rubyforge_project = 'merb'
|
17
|
+
s.name = GEM_NAME
|
18
|
+
s.version = GEM_VERSION
|
19
|
+
s.platform = Gem::Platform::RUBY
|
20
|
+
s.has_rdoc = true
|
21
|
+
s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
|
22
|
+
s.summary = SUMMARY
|
23
|
+
s.description = s.summary
|
24
|
+
s.author = AUTHOR
|
25
|
+
s.email = EMAIL
|
26
|
+
s.homepage = HOMEPAGE
|
27
|
+
s.add_dependency('merb', '>= 1.0.7.1')
|
28
|
+
s.add_development_dependency('merb_full_url', '>= 0.0.2')
|
29
|
+
s.require_path = 'lib'
|
30
|
+
s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,spec}/**/*")
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
35
|
+
pkg.need_tar = true
|
36
|
+
pkg.gem_spec = spec
|
37
|
+
end
|
38
|
+
|
39
|
+
desc "install the plugin as a gem"
|
40
|
+
task :install do
|
41
|
+
Merb::RakeHelper.install(GEM_NAME, :version => GEM_VERSION)
|
42
|
+
end
|
43
|
+
|
44
|
+
desc "Uninstall the gem"
|
45
|
+
task :uninstall do
|
46
|
+
Merb::RakeHelper.uninstall(GEM_NAME, :version => GEM_VERSION)
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "Create a gemspec file"
|
50
|
+
task :gemspec do
|
51
|
+
File.open("#{GEM_NAME}.gemspec", "w") do |file|
|
52
|
+
file.puts spec.to_ruby
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
desc "Run specs"
|
57
|
+
task :spec do
|
58
|
+
sh "spec --color spec"
|
59
|
+
end
|
60
|
+
|
61
|
+
desc 'Package and upload the release to rubyforge.'
|
62
|
+
task :release => :package do |t|
|
63
|
+
require 'rubyforge'
|
64
|
+
v = ENV["VERSION"] or abort "Must supply VERSION=x.y.z"
|
65
|
+
abort "Versions don't match #{v} vs #{GEM_VERSION}" unless v == GEM_VERSION
|
66
|
+
pkg = "pkg/#{GEM_NAME}-#{GEM_VERSION}"
|
67
|
+
|
68
|
+
require 'rubyforge'
|
69
|
+
rf = RubyForge.new.configure
|
70
|
+
puts "Logging in"
|
71
|
+
rf.login
|
72
|
+
|
73
|
+
c = rf.userconfig
|
74
|
+
# c["release_notes"] = description if description
|
75
|
+
# c["release_changes"] = changes if changes
|
76
|
+
c["preformatted"] = true
|
77
|
+
|
78
|
+
files = [
|
79
|
+
"#{pkg}.tgz",
|
80
|
+
"#{pkg}.gem"
|
81
|
+
].compact
|
82
|
+
|
83
|
+
puts "Releasing #{GEM_NAME} v. #{GEM_VERSION}"
|
84
|
+
rf.add_release RUBYFORGE_PROJECT, GEM_NAME, GEM_VERSION, *files
|
85
|
+
end
|
86
|
+
|
87
|
+
task :default => :spec
|
data/TODO
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# make sure we're running inside Merb
|
2
|
+
if defined?(Merb::Plugins)
|
3
|
+
# Merb gives you a Merb::Plugins.config hash...feel free to put your stuff in
|
4
|
+
# your piece of it
|
5
|
+
Merb::Plugins.config[:merb_component] = {
|
6
|
+
}
|
7
|
+
|
8
|
+
Merb::BootLoader.before_app_loads do
|
9
|
+
# require code that must be loaded before the application
|
10
|
+
require 'merb_component/controller_ext'
|
11
|
+
require 'merb_component/resource_ext'
|
12
|
+
require 'merb_component/router_ext'
|
13
|
+
end
|
14
|
+
|
15
|
+
Merb::BootLoader.after_app_loads do
|
16
|
+
# code that can be required after the application loads
|
17
|
+
end
|
18
|
+
|
19
|
+
Merb::Plugins.add_rakefiles "merb_component/merbtasks"
|
20
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
class Merb::Controller
|
2
|
+
METHOD_TO_ACTION = {
|
3
|
+
:post => :create,
|
4
|
+
:put => :update,
|
5
|
+
:delete => :destroy
|
6
|
+
}.freeze
|
7
|
+
|
8
|
+
class << self
|
9
|
+
private
|
10
|
+
def is_component(resource = nil)
|
11
|
+
resource = controller_name.singular if resource.nil?
|
12
|
+
r = resource.to_s
|
13
|
+
m = r.camel_case
|
14
|
+
iv = proc{|i| "(@#{r} = #{m}.#{i})"}
|
15
|
+
ivs = proc{|i| "(@#{r.pluralize} = #{m}.#{i})"}
|
16
|
+
class_eval <<-"RUBY"
|
17
|
+
def index; display #{ivs["all"]} end
|
18
|
+
def show(id) display #{iv["get(id)"]} end
|
19
|
+
def new; display #{iv["new"]} end
|
20
|
+
def edit(id) display #{iv["get(id)"]}; end
|
21
|
+
def create(#{r}) #{iv["create(#{r})"]}; '' end
|
22
|
+
def update(id,#{r}) #{iv["get(id)"]}.update_attributes(#{r}); '' end
|
23
|
+
def destroy(id) #{iv["get(id)"]}.destroy; '' end
|
24
|
+
RUBY
|
25
|
+
end
|
26
|
+
|
27
|
+
def aggregates(aggregation, options = {})
|
28
|
+
if aggregation.is_a?(Symbol)
|
29
|
+
aggregation = {:show => aggregation}
|
30
|
+
end
|
31
|
+
aggregation.each do |agg_action, arg|
|
32
|
+
define_method(arg){} unless method_defined?(arg)
|
33
|
+
model = Object.full_const_get(arg.to_s.singular.camel_case)
|
34
|
+
key = "#{controller_name.singular}_id"
|
35
|
+
var = "@#{arg.to_s.singular}"
|
36
|
+
|
37
|
+
add_filter(_before_filters, proc{|c|
|
38
|
+
id = params.delete(key)
|
39
|
+
method = request.method
|
40
|
+
scope = Mash.new
|
41
|
+
scope[key] = id if id
|
42
|
+
object = nil
|
43
|
+
if action = METHOD_TO_ACTION[method]
|
44
|
+
# setup request
|
45
|
+
req = request.dup
|
46
|
+
req.reset_params!
|
47
|
+
req.instance_variable_set(:@params,
|
48
|
+
params.merge(:controller => arg, :action => action))
|
49
|
+
|
50
|
+
# call action of subsidiary controller with scope
|
51
|
+
cc = Object.full_const_get(params[:action].camel_case).new(req)
|
52
|
+
model.send :with_scope, scope do
|
53
|
+
begin
|
54
|
+
layout = cc.class.default_layout
|
55
|
+
cc.class.layout(options[:layout])
|
56
|
+
cc._abstract_dispatch(action)
|
57
|
+
ensure
|
58
|
+
cc.class.layout(layout)
|
59
|
+
end
|
60
|
+
object = cc.instance_variable_get(var)
|
61
|
+
throw(:halt, proc{
|
62
|
+
target = controller_name.singular.intern
|
63
|
+
target = object.send(target) if object.respond_to?(target)
|
64
|
+
redirect resource(target)
|
65
|
+
}) if object.errors.empty?
|
66
|
+
c.instance_variable_set(var, object)
|
67
|
+
end
|
68
|
+
elsif params[:id]
|
69
|
+
# GET with component id
|
70
|
+
object = model.get(params[:id])
|
71
|
+
c.instance_variable_set(var, object)
|
72
|
+
elsif params[:format]
|
73
|
+
@component_format = params.delete(:format)
|
74
|
+
#c._abstract_dispatch(action)
|
75
|
+
end
|
76
|
+
c.instance_variable_set("#{var}_component", object)
|
77
|
+
|
78
|
+
# prepare for performing actoin of principal controller
|
79
|
+
c.params[:id] = id if id
|
80
|
+
c.params[:action] = c.action_name = agg_action.to_s
|
81
|
+
}, :only => arg)
|
82
|
+
|
83
|
+
add_filter(_after_filters, proc{|c|
|
84
|
+
# setup request
|
85
|
+
request.reset_params!
|
86
|
+
request.instance_variable_set(:@params, params.merge(
|
87
|
+
:controller => arg, :action => :index,
|
88
|
+
:format => @component_format))
|
89
|
+
|
90
|
+
# call index action of subsidiary controller with scope
|
91
|
+
cc = Object.full_const_get(arg.to_s.camel_case).new(request)
|
92
|
+
@body = Aggregator.new(c, cc.class) do
|
93
|
+
cc._abstract_dispatch(:index)
|
94
|
+
end.result
|
95
|
+
}, :only => agg_action, :if => proc{@component_format})
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class Aggregator
|
101
|
+
attr_reader :controller, :object, :result, :context
|
102
|
+
|
103
|
+
def initialize(context, controller, &block)
|
104
|
+
@context = context
|
105
|
+
@controller = controller
|
106
|
+
@agg_name = @context.controller_name.singular.intern
|
107
|
+
model_class = Object.full_const_get(controller.name.singular)
|
108
|
+
@object = @context.instance_variable_get("@#{@agg_name}")
|
109
|
+
@scope = {}
|
110
|
+
|
111
|
+
relationship = model_class.relationships[@agg_name]
|
112
|
+
if @object && relationship
|
113
|
+
Merb.logger.debug [model_class, @agg_name].inspect
|
114
|
+
key_names = relationship.child_key.map{|i| i.name}
|
115
|
+
@scope = Hash[key_names.zip(@object.key)] if @object
|
116
|
+
end
|
117
|
+
|
118
|
+
@result = begin
|
119
|
+
aggregators = Thread::current[:aggregators] ||= {}
|
120
|
+
(aggregators[controller] ||= []).push(self)
|
121
|
+
if model_class.respond_to?(:with_scope)
|
122
|
+
model_class.send(:with_scope, @scope, &block)
|
123
|
+
else
|
124
|
+
block.call
|
125
|
+
end
|
126
|
+
ensure
|
127
|
+
aggregators[controller].pop
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def key
|
132
|
+
@object || @agg_name
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def _abstract_dispatch(*args)
|
137
|
+
_dispatch = Merb::AbstractController.instance_method(:_dispatch)
|
138
|
+
_dispatch.bind(self).call(*args)
|
139
|
+
end
|
140
|
+
|
141
|
+
def aggregator
|
142
|
+
aggregators = Thread::current[:aggregators] ||= {}
|
143
|
+
(aggregators[self.class] ||= []).last
|
144
|
+
end
|
145
|
+
|
146
|
+
def url_with_scope(*args)
|
147
|
+
result = url_without_scope(*args)
|
148
|
+
if (agg = aggregator) && (key = agg.key)
|
149
|
+
resource_without_scope(key) + result
|
150
|
+
else
|
151
|
+
result
|
152
|
+
end
|
153
|
+
end
|
154
|
+
alias_method :url_without_scope, :url
|
155
|
+
alias_method :url, :url_with_scope
|
156
|
+
|
157
|
+
private
|
158
|
+
def component(controller, action, params = {})
|
159
|
+
params = self.params.merge(
|
160
|
+
:controller => controller, :action => action
|
161
|
+
).merge(params)
|
162
|
+
var = "@#{controller.to_s.singular}"
|
163
|
+
object = instance_variable_get("#{var}_component")
|
164
|
+
controller = Object.full_const_get(controller.to_s.camel_case)
|
165
|
+
req = request.dup
|
166
|
+
req.reset_params!
|
167
|
+
req.instance_variable_set :@params, params
|
168
|
+
|
169
|
+
Aggregator.new(this = self, controller) do
|
170
|
+
controller.new(req)._dispatch(action).instance_eval do
|
171
|
+
if object
|
172
|
+
original = instance_variable_get(var)
|
173
|
+
object.attributes = original.attributes if original
|
174
|
+
instance_variable_set(var, object)
|
175
|
+
end
|
176
|
+
cc = this.instance_variable_get(:@_caught_content)
|
177
|
+
instance_variable_get(:@_caught_content).each do |k, v|
|
178
|
+
next if k == :for_layout
|
179
|
+
cc[k].nil? ? this.throw_content(k, v) : this.append_content(k, v)
|
180
|
+
end
|
181
|
+
render :layout => false
|
182
|
+
end
|
183
|
+
end.result
|
184
|
+
end
|
185
|
+
|
186
|
+
def form_for_component(controller, params = {}, &block)
|
187
|
+
var = "@#{controller.to_s.singular}"
|
188
|
+
object = instance_variable_get(var)
|
189
|
+
return nil if object.nil?
|
190
|
+
object = instance_variable_get("#{var}_component") || object
|
191
|
+
if object.new_record?
|
192
|
+
component(controller, :new, params)
|
193
|
+
else
|
194
|
+
component(controller, :edit, {:id => object.id}.merge(params))
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def resource_with_scope(first, *args)
|
199
|
+
agg = aggregator
|
200
|
+
return resource_without_scope(first, *args) unless agg
|
201
|
+
|
202
|
+
controller = case first
|
203
|
+
when Symbol, String
|
204
|
+
Object.full_const_get(first.to_s.camel_case)
|
205
|
+
else
|
206
|
+
Object.full_const_get(first.class.to_s.pluralize.camel_case)
|
207
|
+
end
|
208
|
+
|
209
|
+
if controller <=> agg.controller && agg.key
|
210
|
+
resource_without_scope(agg.key, first, *args)
|
211
|
+
else
|
212
|
+
resource_without_scope(first, *args)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
alias_method :resource_without_scope, :resource
|
216
|
+
alias_method :resource, :resource_with_scope
|
217
|
+
end
|
data/spec/admin_spec.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe Admin do
|
4
|
+
before do
|
5
|
+
@req = Merb::Request.new(
|
6
|
+
Merb::Const::REQUEST_PATH => "/admin",
|
7
|
+
Merb::Const::REQUEST_METHOD => "GET",
|
8
|
+
Merb::Const::QUERY_STRING => "")
|
9
|
+
@c = Admin.new(@req)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should be a controller" do
|
13
|
+
@c.should be_kind_of(Merb::Controller)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "Admin controller" do
|
18
|
+
before :all do
|
19
|
+
@post = Post.create
|
20
|
+
@comment = @post.comments.create(:body => "test")
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should be tested on at least one post and comment" do
|
24
|
+
Post.count.should > 0
|
25
|
+
Comment.count.should > 0
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should show index html" do
|
29
|
+
res = request(resource(:admin))
|
30
|
+
res.should be_successful
|
31
|
+
res.should have_xpath("//h1")
|
32
|
+
res.should have_xpath("//h2")
|
33
|
+
res.should have_xpath("//ul/li")
|
34
|
+
res.should_not have_xpath("//form")
|
35
|
+
res.should have_xpath("//a[@href='/admin/comments?page=1']")
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should show index html for pagination params" do
|
39
|
+
res = request("/admin/comments?page=2")
|
40
|
+
res.should be_successful
|
41
|
+
res.should have_xpath("//h1")
|
42
|
+
res.should have_xpath("//h2")
|
43
|
+
res.should have_xpath("//ul")
|
44
|
+
res.should_not have_xpath("//form")
|
45
|
+
res.should have_xpath("//a[@href='/admin/comments?page=3']")
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should show html after update a comment" do
|
49
|
+
comment = @post.comments.last
|
50
|
+
comment.should be_kind_of(Comment)
|
51
|
+
res = request(resource(:admin, comment),
|
52
|
+
:method => 'PUT', :params => {:comment => {:body => "bar"}})
|
53
|
+
res.should redirect_to("/admin")
|
54
|
+
|
55
|
+
res = request(res.headers["Location"])
|
56
|
+
res.should be_successful
|
57
|
+
res.should have_xpath("//h1")
|
58
|
+
res.should have_xpath("//h2")
|
59
|
+
res.should have_xpath("//ul/li[1]")
|
60
|
+
res.should_not have_xpath("//form")
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should show html after failed to update a comment" do
|
64
|
+
comment = @post.comments.last
|
65
|
+
comment.should be_kind_of(Comment)
|
66
|
+
res = request(resource(:admin, comment),
|
67
|
+
:method => 'PUT', :params => {:comment => {:body => ""}})
|
68
|
+
res.should be_successful
|
69
|
+
res.should have_xpath("//h1")
|
70
|
+
res.should have_xpath("//h2")
|
71
|
+
res.should have_xpath("//ul/li[1]")
|
72
|
+
res.should have_xpath("//form[@action='/admin/comments/#{comment.id}']")
|
73
|
+
res.should have_xpath("//form[@method='post']")
|
74
|
+
res.should have_xpath("//input[@value='put']")
|
75
|
+
res.should have_tag("div.error")
|
76
|
+
res.should have_tag("input.error[@name='comment[body]']")
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should show html after delete a comment" do
|
80
|
+
count = @post.comments.count
|
81
|
+
comment = @post.comments.last
|
82
|
+
comment.should be_kind_of(Comment)
|
83
|
+
res = request(resource(:admin, comment), :method => 'DELETE')
|
84
|
+
res.should redirect_to("/admin")
|
85
|
+
|
86
|
+
res = request(res.headers["Location"])
|
87
|
+
res.should be_successful
|
88
|
+
res.should have_xpath("//h1")
|
89
|
+
res.should have_xpath("//h2")
|
90
|
+
res.should_not have_xpath("//form")
|
91
|
+
@post.comments.count.should == count - 1
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should show html after show a comment" do
|
95
|
+
comment = @post.comments.create(:body => "hello")
|
96
|
+
comment.should_not be_new_record
|
97
|
+
res = request(resource(:admin, comment), :method => 'GET')
|
98
|
+
res.should be_successful
|
99
|
+
res.should have_xpath("//h1")
|
100
|
+
res.should have_xpath("//h2")
|
101
|
+
res.should have_xpath("//form[@action='/admin/comments/#{comment.id}']")
|
102
|
+
res.should have_xpath("//form[@method='post']")
|
103
|
+
res.should have_xpath("//input[@value='put']")
|
104
|
+
res.should_not have_xpath("//body/meta")
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Comments < Application
|
2
|
+
is_component :comment
|
3
|
+
provides :atom, :only => :index
|
4
|
+
|
5
|
+
def new
|
6
|
+
@comment = Comment.new
|
7
|
+
@comment.body = "new"
|
8
|
+
display @comment
|
9
|
+
end
|
10
|
+
|
11
|
+
def edit(id)
|
12
|
+
@comment = Comment.get(id)
|
13
|
+
@comment.body = "edit"
|
14
|
+
display @comment
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<h3>
|
2
|
+
Edit Comment of
|
3
|
+
<%= link_to h(@comment.post.id), resource(@comment.post) %>
|
4
|
+
</h3>
|
5
|
+
|
6
|
+
<%= error_messages_for @comment %>
|
7
|
+
|
8
|
+
<%= form_for @comment, :action => resource(@comment), :method => :put do %>
|
9
|
+
<%= text_field :body %>
|
10
|
+
<%= submit "Submit" %>
|
11
|
+
<% end =%>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
xml.instruct! :xml, :version=>"1.0"
|
2
|
+
xml.feed(:xmlns => "http://www.w3.org/2005/Atom") do |feed|
|
3
|
+
feed.title @title
|
4
|
+
feed.link :type => 'text/html', :rel => 'alternate',
|
5
|
+
:href => full_resource(:comments)
|
6
|
+
|
7
|
+
@comments.each do |comment|
|
8
|
+
feed.entry do |entry|
|
9
|
+
entry.id comment.id
|
10
|
+
entry.title "Comment to post#%s" % comment.post.id
|
11
|
+
entry.content comment.body, :type => 'text'
|
12
|
+
#entry.issued comment.created_at
|
13
|
+
#entry.modified comment.updated_at
|
14
|
+
entry.link :type => "text/html", :rel => "alternate",
|
15
|
+
:href => full_resource(comment)
|
16
|
+
#entry.author do |author|
|
17
|
+
# author.name comment.user.login
|
18
|
+
#end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<h2>Comments</h2>
|
2
|
+
|
3
|
+
<ul>
|
4
|
+
<% @comments.each do |comment| %>
|
5
|
+
<li>
|
6
|
+
<%= link_to h(comment.body), resource(comment) %>
|
7
|
+
</li>
|
8
|
+
<% end %>
|
9
|
+
</ul>
|
10
|
+
|
11
|
+
<%= link_to "next", url(:page => params[:page].to_i + 1) %>
|
12
|
+
|
13
|
+
<% throw_content :for_post do %>
|
14
|
+
test_content
|
15
|
+
<% end %>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
2
|
+
<html xmlns="http://www.w3.org/1999/xhtml"
|
3
|
+
xmlns:v="urn:schemas-microsoft-com:vml" xml:lang="en-us" lang="en-us">
|
4
|
+
<head>
|
5
|
+
<title>merb_component</title>
|
6
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
7
|
+
</head>
|
8
|
+
<body>
|
9
|
+
<%= catch_content :for_layout %>
|
10
|
+
</body>
|
11
|
+
</html>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "merb_component" do
|
4
|
+
it "should extend controller" do
|
5
|
+
Posts.private_instance_methods.should be_include("component")
|
6
|
+
Posts.private_instance_methods.should be_include("form_for_component")
|
7
|
+
Posts.instance_methods.should be_include("aggregator")
|
8
|
+
Posts.private_instance_methods.should be_include("resource")
|
9
|
+
Posts.public_instance_methods.should be_include("url")
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should not extend model" do
|
13
|
+
Post.public_methods.should_not be_include("related_with")
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should accept symbol as the first param of compoent" do
|
17
|
+
req = Merb::Request.new({})
|
18
|
+
c = Posts.new(req)
|
19
|
+
proc do
|
20
|
+
c.send(:component, :comments, :index)
|
21
|
+
end.should raise_error(Merb::Router::GenerationError)
|
22
|
+
c.instance_variable_set(:@post, Post.create)
|
23
|
+
result = c.send(:component, :comments, :index)
|
24
|
+
result.should be_kind_of(String)
|
25
|
+
end
|
26
|
+
end
|
data/spec/posts_spec.rb
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe Posts do
|
4
|
+
before do
|
5
|
+
@req = Merb::Request.new(
|
6
|
+
Merb::Const::REQUEST_PATH => "/posts",
|
7
|
+
Merb::Const::REQUEST_METHOD => "GET",
|
8
|
+
Merb::Const::QUERY_STRING => "")
|
9
|
+
@c = Posts.new(@req)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should be a controller" do
|
13
|
+
@c.should be_kind_of(Merb::Controller)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "Posts controller" do
|
18
|
+
before :all do
|
19
|
+
@post = Post.create
|
20
|
+
@comment = @post.comments.create(:body => "test")
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should be tested on at least one post" do
|
24
|
+
Post.count.should > 0
|
25
|
+
Comment.count.should > 0
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should show html" do
|
29
|
+
res = request(resource(@post))
|
30
|
+
res.should be_successful
|
31
|
+
res.should have_xpath("//h1")
|
32
|
+
res.should have_xpath("//h2")
|
33
|
+
res.should have_xpath("//ul/li")
|
34
|
+
res.should have_xpath("//form[@method='post']")
|
35
|
+
res.should have_xpath("//form[@action='/posts/#{@post.id}/comments']")
|
36
|
+
res.should_not have_xpath("//input[@value='put']")
|
37
|
+
res.should have_xpath("//input[@value='new']")
|
38
|
+
res.should have_xpath("//a[@href='/posts/#{@post.id}/comments?page=1']")
|
39
|
+
res.should contain("test_content")
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should show html for pagination params" do
|
43
|
+
res = request("/posts/#{@post.id}/comments?page=2")
|
44
|
+
res.should be_successful
|
45
|
+
res.should have_xpath("//h1")
|
46
|
+
res.should have_xpath("//h2")
|
47
|
+
res.should have_xpath("//ul/li")
|
48
|
+
res.should have_xpath("//form[@method='post']")
|
49
|
+
res.should have_xpath("//form[@action='/posts/#{@post.id}/comments']")
|
50
|
+
res.should_not have_xpath("//input[@value='put']")
|
51
|
+
res.should have_xpath("//input[@value='new']")
|
52
|
+
res.should have_xpath("//a[@href='/posts/#{@post.id}/comments?page=3']")
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should show html after post a comment" do
|
56
|
+
count = @post.comments.count
|
57
|
+
res = request(resource(@post, :comments),
|
58
|
+
:method => 'POST', :params => {:comment => {:body => "foo"}})
|
59
|
+
res.should redirect_to("/posts/#{@post.id}")
|
60
|
+
|
61
|
+
res = request(res.headers["Location"])
|
62
|
+
res.should be_successful
|
63
|
+
res.should have_xpath("//h1")
|
64
|
+
res.should have_xpath("//h2")
|
65
|
+
res.should have_xpath("//ul/li[1]")
|
66
|
+
res.should have_xpath("//ul/li[2]")
|
67
|
+
res.should have_xpath("//form[@method='post']")
|
68
|
+
res.should have_xpath("//form[@action='/posts/#{@post.id}/comments']")
|
69
|
+
res.should_not have_xpath("//input[@value='put']")
|
70
|
+
res.should contain("foo")
|
71
|
+
Comment.all(:post_id => @post.id).count.should == count + 1
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should show html after failed to post a comment" do
|
75
|
+
count = Comment.all(:post_id => @post.id).count
|
76
|
+
res = request(resource(@post, :comments),
|
77
|
+
:method => 'POST', :params => {:comment => {:body => ""}})
|
78
|
+
res.should be_successful
|
79
|
+
res.should have_xpath("//h1")
|
80
|
+
res.should have_xpath("//h2")
|
81
|
+
res.should have_xpath("//ul/li[1]")
|
82
|
+
res.should have_xpath("//ul/li[2]")
|
83
|
+
res.should have_xpath("//form[@method='post']")
|
84
|
+
res.should have_xpath("//form[@action='/posts/#{@post.id}/comments']")
|
85
|
+
res.should_not have_xpath("//input[@value='put']")
|
86
|
+
res.should have_tag("div.error")
|
87
|
+
res.should have_tag("input.error[@name='comment[body]']")
|
88
|
+
Comment.all(:post_id => @post.id).count.should == count
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should show html after update a comment" do
|
92
|
+
comment = @post.comments.last
|
93
|
+
comment.should be_kind_of(Comment)
|
94
|
+
res = request(resource(@post, comment),
|
95
|
+
:method => 'PUT', :params => {:comment => {:body => "bar"}})
|
96
|
+
res.should redirect_to("/posts/#{@post.id}")
|
97
|
+
|
98
|
+
res = request(res.headers["Location"])
|
99
|
+
res.should be_successful
|
100
|
+
res.should have_xpath("//h1")
|
101
|
+
res.should have_xpath("//h2")
|
102
|
+
res.should have_xpath("//ul/li[1]")
|
103
|
+
res.should have_xpath("//ul/li[2]")
|
104
|
+
res.should have_xpath("//form[@method='post']")
|
105
|
+
res.should have_xpath("//form[@action='/posts/#{@post.id}/comments']")
|
106
|
+
res.should_not have_xpath("//input[@value='put']")
|
107
|
+
res.should contain("bar")
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should show html after failed to update a comment" do
|
111
|
+
comment = @post.comments.last
|
112
|
+
comment.should be_kind_of(Comment)
|
113
|
+
res = request(resource(@post, comment),
|
114
|
+
:method => 'PUT', :params => {:comment => {:body => ""}})
|
115
|
+
res.should be_successful
|
116
|
+
res.should have_xpath("//h1")
|
117
|
+
res.should have_xpath("//h2")
|
118
|
+
res.should have_xpath("//ul/li[1]")
|
119
|
+
res.should have_xpath("//ul/li[2]")
|
120
|
+
res.should have_xpath("//form[@method='post']")
|
121
|
+
url = "/posts/#{@post.id}/comments/#{comment.id}"
|
122
|
+
res.should have_xpath("//form[@action='#{url}']")
|
123
|
+
res.should have_xpath("//input[@value='put']")
|
124
|
+
res.should have_tag("div.error")
|
125
|
+
res.should have_tag("input.error[@name='comment[body]']")
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should show html after delete a comment" do
|
129
|
+
count = @post.comments.count
|
130
|
+
comment = @post.comments.last
|
131
|
+
comment.should be_kind_of(Comment)
|
132
|
+
res = request(resource(@post, comment), :method => 'DELETE')
|
133
|
+
res.should redirect_to("/posts/#{@post.id}")
|
134
|
+
|
135
|
+
res = request(res.headers["Location"])
|
136
|
+
res.should be_successful
|
137
|
+
res.should have_xpath("//h1")
|
138
|
+
res.should have_xpath("//h2")
|
139
|
+
res.should have_xpath("//form[@action='/posts/#{@post.id}/comments']")
|
140
|
+
res.should have_xpath("//form[@method='post']")
|
141
|
+
res.should_not have_xpath("//input[@value='put']")
|
142
|
+
@post.comments.count.should == count - 1
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should show html after show a comment" do
|
146
|
+
comment = @post.comments.create(:body => "test")
|
147
|
+
comment.should_not be_new_record
|
148
|
+
res = request(resource(@post, comment), :method => 'GET')
|
149
|
+
res.should be_successful
|
150
|
+
res.should have_xpath("//h1")
|
151
|
+
res.should have_xpath("//h2")
|
152
|
+
url = "/posts/#{@post.id}/comments/#{comment.id}"
|
153
|
+
res.should have_xpath("//form[@action='#{url}']")
|
154
|
+
res.should have_xpath("//input[@value='put']")
|
155
|
+
res.should_not have_xpath("//body/meta")
|
156
|
+
res.should have_xpath("//input[@value='edit']")
|
157
|
+
|
158
|
+
pending "should check pagination"
|
159
|
+
end
|
160
|
+
|
161
|
+
it "should provide atom feed for comments" do
|
162
|
+
comment = @post.comments.create(:body => "test")
|
163
|
+
comment.should_not be_new_record
|
164
|
+
res = request(resource(@post, :comments, :format => :atom))
|
165
|
+
res.should be_successful
|
166
|
+
res.should have_xpath("//feed/title")
|
167
|
+
url = "http://example.org/posts/#{@post.id}/comments"
|
168
|
+
res.should have_xpath("//feed/link[@href='#{url}']")
|
169
|
+
res.should have_xpath("//entry/title")
|
170
|
+
url = "http://example.org/posts/#{@post.id}/comments/#{comment.id}"
|
171
|
+
res.should have_xpath("//entry/link[@href='#{url}']")
|
172
|
+
end
|
173
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
$:.push File.join(File.dirname(__FILE__), '..', 'lib')
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'merb-core'
|
5
|
+
require 'merb-core/plugins'
|
6
|
+
require 'merb_component'
|
7
|
+
require "spec" # Satisfies Autotest and anyone else not using the Rake tasks
|
8
|
+
|
9
|
+
dependency "dm-core"
|
10
|
+
dependency "dm-aggregates"
|
11
|
+
dependency "merb-action-args"
|
12
|
+
dependency "merb-helpers"
|
13
|
+
dependency "merb-assets"
|
14
|
+
dependency "dm-validations"
|
15
|
+
dependency "merb-builder"
|
16
|
+
dependency "merb_full_url"
|
17
|
+
|
18
|
+
use_orm :datamapper
|
19
|
+
use_test :rspec
|
20
|
+
use_template_engine :erb
|
21
|
+
|
22
|
+
# this loads all plugins required in your init file so don't add them
|
23
|
+
# here again, Merb will do it for you
|
24
|
+
Merb.disable(:initfile)
|
25
|
+
Merb.start_environment(
|
26
|
+
:testing => true,
|
27
|
+
:adapter => 'runner',
|
28
|
+
:environment => ENV['MERB_ENV'] || 'test',
|
29
|
+
:merb_root => File.dirname(__FILE__) / 'fixture',
|
30
|
+
:log_file => File.dirname(__FILE__) / '..' / "merb_test.log"
|
31
|
+
)
|
32
|
+
DataMapper.setup(:default, "sqlite3::memory:")
|
33
|
+
Merb.add_mime_type(:atom, :to_atom, %w[application/atom+xml])
|
34
|
+
|
35
|
+
Spec::Runner.configure do |config|
|
36
|
+
config.include(Merb::Test::ViewHelper)
|
37
|
+
config.include(Merb::Test::RouteHelper)
|
38
|
+
config.include(Merb::Test::ControllerHelper)
|
39
|
+
config.before(:all){DataMapper.auto_migrate!}
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: merb_component
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Genki Takiuchi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-27 00:00:00 +09:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: merb
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.0.7.1
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: merb_full_url
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.0.2
|
34
|
+
version:
|
35
|
+
description: Merb plugin that provides composition of controllers.
|
36
|
+
email: genki@s21g.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- README
|
43
|
+
- LICENSE
|
44
|
+
- TODO
|
45
|
+
files:
|
46
|
+
- LICENSE
|
47
|
+
- README
|
48
|
+
- Rakefile
|
49
|
+
- TODO
|
50
|
+
- lib/merb_component
|
51
|
+
- lib/merb_component/controller_ext.rb
|
52
|
+
- lib/merb_component/merbtasks.rb
|
53
|
+
- lib/merb_component/resource_ext.rb
|
54
|
+
- lib/merb_component/router_ext.rb
|
55
|
+
- lib/merb_component.rb
|
56
|
+
- spec/admin_spec.rb
|
57
|
+
- spec/fixture
|
58
|
+
- spec/fixture/app
|
59
|
+
- spec/fixture/app/controllers
|
60
|
+
- spec/fixture/app/controllers/admin.rb
|
61
|
+
- spec/fixture/app/controllers/application.rb
|
62
|
+
- spec/fixture/app/controllers/comments.rb
|
63
|
+
- spec/fixture/app/controllers/posts.rb
|
64
|
+
- spec/fixture/app/models
|
65
|
+
- spec/fixture/app/models/comment.rb
|
66
|
+
- spec/fixture/app/models/post.rb
|
67
|
+
- spec/fixture/app/views
|
68
|
+
- spec/fixture/app/views/admin
|
69
|
+
- spec/fixture/app/views/admin/show.html.erb
|
70
|
+
- spec/fixture/app/views/comments
|
71
|
+
- spec/fixture/app/views/comments/edit.html.erb
|
72
|
+
- spec/fixture/app/views/comments/index.atom.builder
|
73
|
+
- spec/fixture/app/views/comments/index.html.erb
|
74
|
+
- spec/fixture/app/views/comments/new.html.erb
|
75
|
+
- spec/fixture/app/views/layout
|
76
|
+
- spec/fixture/app/views/layout/application.html.erb
|
77
|
+
- spec/fixture/app/views/posts
|
78
|
+
- spec/fixture/app/views/posts/index.html.erb
|
79
|
+
- spec/fixture/app/views/posts/show.html.erb
|
80
|
+
- spec/fixture/config
|
81
|
+
- spec/fixture/config/router.rb
|
82
|
+
- spec/merb_component_spec.rb
|
83
|
+
- spec/posts_spec.rb
|
84
|
+
- spec/spec_helper.rb
|
85
|
+
has_rdoc: true
|
86
|
+
homepage: http://blog.s21g.com/genki
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: "0"
|
97
|
+
version:
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: "0"
|
103
|
+
version:
|
104
|
+
requirements: []
|
105
|
+
|
106
|
+
rubyforge_project: merb
|
107
|
+
rubygems_version: 1.3.1
|
108
|
+
signing_key:
|
109
|
+
specification_version: 2
|
110
|
+
summary: Merb plugin that provides composition of controllers.
|
111
|
+
test_files: []
|
112
|
+
|