merb_component 0.2.3

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) 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
+