merb_component 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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,5 @@
1
+ TODO:
2
+ Fix LICENSE with your name
3
+ Fix Rakefile with your name and contact info
4
+ Add your code to lib/merb_component.rb
5
+ Add your Merb rake tasks to lib/merb_component/merbtasks.rb
@@ -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
@@ -0,0 +1,6 @@
1
+ namespace :merb_component do
2
+ desc "Do something for merb_component"
3
+ task :default do
4
+ puts "merb_component doesn't do anything"
5
+ end
6
+ end
@@ -0,0 +1,11 @@
1
+ module DataMapper::Resource
2
+ module ClassMethods
3
+ # set scope
4
+ def new(attrs = {})
5
+ collection = all
6
+ collection.repository.scope do
7
+ super(collection.default_attributes.merge(attrs))
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ Merb::Router.extensions do
2
+ def aggregates(resource, options = {})
3
+ options[:controller] ||= @params[:controller]
4
+ match("/:action/:id").to(options)
5
+ match("/:action(.:format)").to(options)
6
+ resources resource
7
+ end
8
+ end
@@ -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,7 @@
1
+ class Admin < Application
2
+ aggregates :show => :comments
3
+
4
+ def show
5
+ render
6
+ end
7
+ end
@@ -0,0 +1,2 @@
1
+ class Application < Merb::Controller
2
+ 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,14 @@
1
+ class Posts < Application
2
+ aggregates :comments
3
+
4
+ def index
5
+ @posts = Post.all
6
+ display @posts
7
+ end
8
+
9
+ def show(id)
10
+ @post = Post.get(id)
11
+ @comment = @post.comments.build
12
+ display @post
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ class Comment
2
+ include DataMapper::Resource
3
+
4
+ property :id, Serial
5
+ property :body, Text
6
+
7
+ belongs_to :post
8
+
9
+ validates_present :body
10
+ end
@@ -0,0 +1,7 @@
1
+ class Post
2
+ include DataMapper::Resource
3
+
4
+ property :id, Serial
5
+
6
+ has n, :comments
7
+ end
@@ -0,0 +1,4 @@
1
+ <h1>Admin</h1>
2
+
3
+ <%= component :comments, :index %>
4
+ <%= form_for_component :comments %>
@@ -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,6 @@
1
+ <%= error_messages_for @comment %>
2
+
3
+ <%= form_for @comment, :action => resource(:comments) do %>
4
+ <%= text_field :body %>
5
+ <%= submit "submit" %>
6
+ <% 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,7 @@
1
+ <h1>Posts</h1>
2
+
3
+ <ul>
4
+ <% @posts.each do |post| %>
5
+ <li><%= post.id %></li>
6
+ <% end %>
7
+ </ul>
@@ -0,0 +1,6 @@
1
+ <h1><%= link_to @post.id.to_s, resource(@post) %></h1>
2
+
3
+ <%= component :comments, :index %>
4
+ <%= form_for_component :comments %>
5
+
6
+ <%= catch_content :for_post %>
@@ -0,0 +1,11 @@
1
+ Merb::Router.prepare do
2
+ resources :comments
3
+ resources :posts do |post|
4
+ post.aggregates :comments
5
+ end
6
+ resource :admin, :controller => :admin do |admin|
7
+ admin.aggregates :comments
8
+ end
9
+
10
+ default_routes
11
+ end
@@ -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
@@ -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
@@ -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
+